fix: enforce ServerApi as the wire authority
This commit is contained in:
+214
-118
@@ -537,8 +537,13 @@ pub trait ServerApi {
|
|||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
#[path] operation_id: String,
|
#[path] operation_id: String,
|
||||||
) -> Result<WorkspaceDeletionOperationResponse, RepositoryApiError>;
|
) -> Result<WorkspaceDeletionOperationResponse, RepositoryApiError>;
|
||||||
|
#[get(
|
||||||
#[get("/api/workspace", status = 200, error_status = 400)]
|
"/api/workspace",
|
||||||
|
status = 200,
|
||||||
|
error_status = 400,
|
||||||
|
bearer_auth = true,
|
||||||
|
browser_auth = true
|
||||||
|
)]
|
||||||
async fn workspace_current(
|
async fn workspace_current(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -833,12 +838,13 @@ pub trait ServerApi {
|
|||||||
&self,
|
&self,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
) -> Result<WorkspaceConfigTreeResponse, RepositoryApiError>;
|
) -> Result<WorkspaceConfigTreeResponse, RepositoryApiError>;
|
||||||
|
|
||||||
#[get(
|
#[get(
|
||||||
"/api/w/{workspace_id}/config/projections/prompts",
|
"/api/w/{workspace_id}/config/projections/prompts",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 500]
|
additional_error_statuses = [401, 403, 404, 500],
|
||||||
|
bearer_auth = true,
|
||||||
|
browser_auth = true,
|
||||||
)]
|
)]
|
||||||
async fn workspace_prompt_projection(
|
async fn workspace_prompt_projection(
|
||||||
&self,
|
&self,
|
||||||
@@ -899,47 +905,51 @@ pub trait ServerApi {
|
|||||||
&self,
|
&self,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
) -> Result<ProfileSettingsResponse, RepositoryApiError>;
|
) -> Result<ProfileSettingsResponse, RepositoryApiError>;
|
||||||
|
|
||||||
#[get(
|
#[get(
|
||||||
"/api/w/{workspace_id}/flows",
|
"/api/w/{workspace_id}/flows",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 500]
|
additional_error_statuses = [401, 403, 404, 500],
|
||||||
|
bearer_auth = true,
|
||||||
|
browser_auth = true,
|
||||||
)]
|
)]
|
||||||
async fn flow_list(
|
async fn flow_list(
|
||||||
&self,
|
&self,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
) -> Result<FlowSourceListResponse, RepositoryApiError>;
|
) -> Result<FlowSourceListResponse, RepositoryApiError>;
|
||||||
|
|
||||||
#[put(
|
#[put(
|
||||||
"/api/w/{workspace_id}/flows",
|
"/api/w/{workspace_id}/flows",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 409, 500]
|
additional_error_statuses = [401, 403, 404, 409, 500],
|
||||||
|
bearer_auth = true,
|
||||||
|
browser_auth = true,
|
||||||
)]
|
)]
|
||||||
async fn flow_put(
|
async fn flow_put(
|
||||||
&self,
|
&self,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[body] request: PutFlowRequest,
|
#[body] request: PutFlowRequest,
|
||||||
) -> Result<FlowSourceRecord, RepositoryApiError>;
|
) -> Result<FlowSourceRecord, RepositoryApiError>;
|
||||||
|
|
||||||
#[post(
|
#[post(
|
||||||
"/api/w/{workspace_id}/flows/resolve",
|
"/api/w/{workspace_id}/flows/resolve",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 500]
|
additional_error_statuses = [401, 403, 404, 500],
|
||||||
|
bearer_auth = true,
|
||||||
|
browser_auth = true,
|
||||||
)]
|
)]
|
||||||
async fn flow_resolve(
|
async fn flow_resolve(
|
||||||
&self,
|
&self,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[body] request: FlowSourceResolveRequest,
|
#[body] request: FlowSourceResolveRequest,
|
||||||
) -> Result<ResolvedFlowSource, RepositoryApiError>;
|
) -> Result<ResolvedFlowSource, RepositoryApiError>;
|
||||||
|
|
||||||
#[get(
|
#[get(
|
||||||
"/api/w/{workspace_id}/flows/{flow_id}",
|
"/api/w/{workspace_id}/flows/{flow_id}",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 404,
|
error_status = 404,
|
||||||
additional_error_statuses = [400, 401, 403, 500]
|
additional_error_statuses = [400, 401, 403, 500],
|
||||||
|
bearer_auth = true,
|
||||||
|
browser_auth = true,
|
||||||
)]
|
)]
|
||||||
async fn flow_get(
|
async fn flow_get(
|
||||||
&self,
|
&self,
|
||||||
@@ -973,24 +983,26 @@ pub trait ServerApi {
|
|||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[query] query: MemoryStagingQuery,
|
#[query] query: MemoryStagingQuery,
|
||||||
) -> Result<MemoryStagingListResponse, RepositoryApiError>;
|
) -> Result<MemoryStagingListResponse, RepositoryApiError>;
|
||||||
|
|
||||||
#[post(
|
#[post(
|
||||||
"/api/w/{workspace_id}/memory/backend",
|
"/api/w/{workspace_id}/memory/backend",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 500]
|
additional_error_statuses = [401, 403, 404, 500],
|
||||||
|
bearer_auth = true,
|
||||||
|
browser_auth = true,
|
||||||
)]
|
)]
|
||||||
async fn memory_backend(
|
async fn memory_backend(
|
||||||
&self,
|
&self,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[body] request: MemoryBackendRequest,
|
#[body] request: MemoryBackendRequest,
|
||||||
) -> Result<MemoryBackendResponse, RepositoryApiError>;
|
) -> Result<MemoryBackendResponse, RepositoryApiError>;
|
||||||
|
|
||||||
#[post(
|
#[post(
|
||||||
"/api/w/{workspace_id}/memory/consolidation",
|
"/api/w/{workspace_id}/memory/consolidation",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503]
|
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503],
|
||||||
|
bearer_auth = true,
|
||||||
|
browser_auth = true,
|
||||||
)]
|
)]
|
||||||
async fn memory_consolidation(
|
async fn memory_consolidation(
|
||||||
&self,
|
&self,
|
||||||
@@ -1037,12 +1049,13 @@ pub trait ServerApi {
|
|||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[path] name: String,
|
#[path] name: String,
|
||||||
) -> Result<SkillDetailResponse, RepositoryApiError>;
|
) -> Result<SkillDetailResponse, RepositoryApiError>;
|
||||||
|
|
||||||
#[get(
|
#[get(
|
||||||
"/api/w/{workspace_id}/skills/{name}/activate",
|
"/api/w/{workspace_id}/skills/{name}/activate",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 404,
|
error_status = 404,
|
||||||
additional_error_statuses = [400, 401, 403, 409, 500]
|
additional_error_statuses = [400, 401, 403, 409, 500],
|
||||||
|
bearer_auth = true,
|
||||||
|
browser_auth = true,
|
||||||
)]
|
)]
|
||||||
async fn skill_activate(
|
async fn skill_activate(
|
||||||
&self,
|
&self,
|
||||||
@@ -1112,24 +1125,24 @@ pub trait ServerApi {
|
|||||||
&self,
|
&self,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
) -> Result<BrowserWorkspaceOrchestratorResponse, RepositoryApiError>;
|
) -> Result<BrowserWorkspaceOrchestratorResponse, RepositoryApiError>;
|
||||||
|
|
||||||
#[get(
|
#[get(
|
||||||
"/api/w/{workspace_id}/worker-control/workers",
|
"/api/w/{workspace_id}/worker-control/workers",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 500]
|
additional_error_statuses = [401, 403, 404, 500],
|
||||||
|
openapi = false,
|
||||||
)]
|
)]
|
||||||
async fn worker_control_list(
|
async fn worker_control_list(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
) -> Result<WorkerControlListResponse, RepositoryApiError>;
|
) -> Result<WorkerControlListResponse, RepositoryApiError>;
|
||||||
|
|
||||||
#[post(
|
#[post(
|
||||||
"/api/w/{workspace_id}/worker-control/workers",
|
"/api/w/{workspace_id}/worker-control/workers",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503]
|
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503],
|
||||||
|
openapi = false,
|
||||||
)]
|
)]
|
||||||
async fn worker_control_spawn(
|
async fn worker_control_spawn(
|
||||||
&self,
|
&self,
|
||||||
@@ -1137,12 +1150,12 @@ pub trait ServerApi {
|
|||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[body] request: CreateWorkspaceWorkerRequest,
|
#[body] request: CreateWorkspaceWorkerRequest,
|
||||||
) -> Result<BrowserCreateWorkerResponse, RepositoryApiError>;
|
) -> Result<BrowserCreateWorkerResponse, RepositoryApiError>;
|
||||||
|
|
||||||
#[post(
|
#[post(
|
||||||
"/api/w/{workspace_id}/worker-control/workers/{runtime_id}/{worker_id}/input",
|
"/api/w/{workspace_id}/worker-control/workers/{runtime_id}/{worker_id}/input",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503]
|
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503],
|
||||||
|
openapi = false,
|
||||||
)]
|
)]
|
||||||
async fn worker_control_input(
|
async fn worker_control_input(
|
||||||
&self,
|
&self,
|
||||||
@@ -1152,12 +1165,12 @@ pub trait ServerApi {
|
|||||||
#[path] worker_id: String,
|
#[path] worker_id: String,
|
||||||
#[body] request: RuntimeWorkerInputRequest,
|
#[body] request: RuntimeWorkerInputRequest,
|
||||||
) -> Result<RuntimeWorkerInputResult, RepositoryApiError>;
|
) -> Result<RuntimeWorkerInputResult, RepositoryApiError>;
|
||||||
|
|
||||||
#[post(
|
#[post(
|
||||||
"/api/w/{workspace_id}/worker-control/workers/{runtime_id}/{worker_id}/cancel",
|
"/api/w/{workspace_id}/worker-control/workers/{runtime_id}/{worker_id}/cancel",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503]
|
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503],
|
||||||
|
openapi = false,
|
||||||
)]
|
)]
|
||||||
async fn worker_control_cancel(
|
async fn worker_control_cancel(
|
||||||
&self,
|
&self,
|
||||||
@@ -1167,12 +1180,12 @@ pub trait ServerApi {
|
|||||||
#[path] worker_id: String,
|
#[path] worker_id: String,
|
||||||
#[body] request: RuntimeWorkerLifecycleRequest,
|
#[body] request: RuntimeWorkerLifecycleRequest,
|
||||||
) -> Result<RuntimeWorkerLifecycleResult, RepositoryApiError>;
|
) -> Result<RuntimeWorkerLifecycleResult, RepositoryApiError>;
|
||||||
|
|
||||||
#[post(
|
#[post(
|
||||||
"/api/w/{workspace_id}/worker-control/workers/{runtime_id}/{worker_id}/stop",
|
"/api/w/{workspace_id}/worker-control/workers/{runtime_id}/{worker_id}/stop",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503, 504]
|
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503, 504],
|
||||||
|
openapi = false,
|
||||||
)]
|
)]
|
||||||
async fn worker_control_stop(
|
async fn worker_control_stop(
|
||||||
&self,
|
&self,
|
||||||
@@ -1182,12 +1195,12 @@ pub trait ServerApi {
|
|||||||
#[path] worker_id: String,
|
#[path] worker_id: String,
|
||||||
#[body] request: RuntimeWorkerLifecycleRequest,
|
#[body] request: RuntimeWorkerLifecycleRequest,
|
||||||
) -> Result<RuntimeWorkerLifecycleResult, RepositoryApiError>;
|
) -> Result<RuntimeWorkerLifecycleResult, RepositoryApiError>;
|
||||||
|
|
||||||
#[post(
|
#[post(
|
||||||
"/api/w/{workspace_id}/worker-control/workers/{runtime_id}/{worker_id}/restore",
|
"/api/w/{workspace_id}/worker-control/workers/{runtime_id}/{worker_id}/restore",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503, 504]
|
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503, 504],
|
||||||
|
openapi = false,
|
||||||
)]
|
)]
|
||||||
async fn worker_control_restore(
|
async fn worker_control_restore(
|
||||||
&self,
|
&self,
|
||||||
@@ -1196,26 +1209,24 @@ pub trait ServerApi {
|
|||||||
#[path] runtime_id: String,
|
#[path] runtime_id: String,
|
||||||
#[path] worker_id: String,
|
#[path] worker_id: String,
|
||||||
) -> Result<WorkerRestoreResponse, RepositoryApiError>;
|
) -> Result<WorkerRestoreResponse, RepositoryApiError>;
|
||||||
|
|
||||||
#[get(
|
#[get(
|
||||||
"/api/w/{workspace_id}/worker-observation/sessions",
|
"/api/w/{workspace_id}/worker-observation/sessions",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 500],
|
additional_error_statuses = [401, 403, 404, 500],
|
||||||
openapi = false
|
openapi = false,
|
||||||
)]
|
)]
|
||||||
async fn worker_observation_sessions(
|
async fn worker_observation_sessions(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
) -> Result<WorkerObservationSessionsResponse, RepositoryApiError>;
|
) -> Result<WorkerObservationSessionsResponse, RepositoryApiError>;
|
||||||
|
|
||||||
#[post(
|
#[post(
|
||||||
"/api/w/{workspace_id}/worker-observation/session",
|
"/api/w/{workspace_id}/worker-observation/session",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 500, 502, 504],
|
additional_error_statuses = [401, 403, 404, 500, 502, 504],
|
||||||
openapi = false
|
openapi = false,
|
||||||
)]
|
)]
|
||||||
async fn worker_observation_capture(
|
async fn worker_observation_capture(
|
||||||
&self,
|
&self,
|
||||||
@@ -1223,12 +1234,12 @@ pub trait ServerApi {
|
|||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[body] request: WorkerObservationSubjectRef,
|
#[body] request: WorkerObservationSubjectRef,
|
||||||
) -> Result<WorkerObservationCaptureResponse, RepositoryApiError>;
|
) -> Result<WorkerObservationCaptureResponse, RepositoryApiError>;
|
||||||
|
|
||||||
#[get(
|
#[get(
|
||||||
"/api/w/{workspace_id}/worker-discovery/workers",
|
"/api/w/{workspace_id}/worker-discovery/workers",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 500]
|
additional_error_statuses = [401, 403, 404, 500],
|
||||||
|
openapi = false,
|
||||||
)]
|
)]
|
||||||
async fn workspace_worker_discovery(
|
async fn workspace_worker_discovery(
|
||||||
&self,
|
&self,
|
||||||
@@ -1303,12 +1314,12 @@ pub trait ServerApi {
|
|||||||
&self,
|
&self,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
) -> Result<WorkerLaunchOptionsResponse, RepositoryApiError>;
|
) -> Result<WorkerLaunchOptionsResponse, RepositoryApiError>;
|
||||||
|
|
||||||
#[post(
|
#[post(
|
||||||
"/api/runtime/v1/workspaces/{workspace_id}/resources/fetch",
|
"/api/runtime/v1/workspaces/{workspace_id}/resources/fetch",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 404, 410, 502, 504]
|
additional_error_statuses = [401, 404, 410, 502, 504],
|
||||||
|
openapi = false,
|
||||||
)]
|
)]
|
||||||
async fn runtime_resource_fetch(
|
async fn runtime_resource_fetch(
|
||||||
&self,
|
&self,
|
||||||
@@ -1420,12 +1431,12 @@ pub trait ServerApi {
|
|||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[body] request: CompanionCancelRequest,
|
#[body] request: CompanionCancelRequest,
|
||||||
) -> Result<CompanionMessageResponse, RepositoryApiError>;
|
) -> Result<CompanionMessageResponse, RepositoryApiError>;
|
||||||
|
|
||||||
#[post(
|
#[post(
|
||||||
"/api/w/{workspace_id}/workers/remove",
|
"/api/w/{workspace_id}/workers/remove",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 409, 500, 503]
|
additional_error_statuses = [401, 403, 404, 409, 500, 503],
|
||||||
|
openapi = false,
|
||||||
)]
|
)]
|
||||||
async fn workspace_worker_remove(
|
async fn workspace_worker_remove(
|
||||||
&self,
|
&self,
|
||||||
@@ -2214,13 +2225,12 @@ pub trait ServerApi {
|
|||||||
#[path] working_directory_id: String,
|
#[path] working_directory_id: String,
|
||||||
#[body] request: WorkingDirectoryRemovalRequest,
|
#[body] request: WorkingDirectoryRemovalRequest,
|
||||||
) -> Result<WorkingDirectoryRemovalResponse, RepositoryApiError>;
|
) -> Result<WorkingDirectoryRemovalResponse, RepositoryApiError>;
|
||||||
|
|
||||||
#[post(
|
#[post(
|
||||||
"/api/w/{workspace_id}/workers/self/workdir-attachments",
|
"/api/w/{workspace_id}/workers/self/workdir-attachments",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503],
|
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503],
|
||||||
bearer_auth = true
|
openapi = false,
|
||||||
)]
|
)]
|
||||||
async fn current_worker_workdir_attach(
|
async fn current_worker_workdir_attach(
|
||||||
&self,
|
&self,
|
||||||
@@ -2228,13 +2238,12 @@ pub trait ServerApi {
|
|||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[body] request: CurrentWorkerWorkdirAttachRequest,
|
#[body] request: CurrentWorkerWorkdirAttachRequest,
|
||||||
) -> Result<CurrentWorkerWorkdirAttachmentResponse, RepositoryApiError>;
|
) -> Result<CurrentWorkerWorkdirAttachmentResponse, RepositoryApiError>;
|
||||||
|
|
||||||
#[delete(
|
#[delete(
|
||||||
"/api/w/{workspace_id}/workers/self/workdir-attachments/{alias}",
|
"/api/w/{workspace_id}/workers/self/workdir-attachments/{alias}",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503],
|
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503],
|
||||||
bearer_auth = true
|
openapi = false,
|
||||||
)]
|
)]
|
||||||
async fn current_worker_workdir_detach(
|
async fn current_worker_workdir_detach(
|
||||||
&self,
|
&self,
|
||||||
@@ -2242,13 +2251,12 @@ pub trait ServerApi {
|
|||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[path] alias: String,
|
#[path] alias: String,
|
||||||
) -> Result<CurrentWorkerWorkdirAttachmentResponse, RepositoryApiError>;
|
) -> Result<CurrentWorkerWorkdirAttachmentResponse, RepositoryApiError>;
|
||||||
|
|
||||||
#[post(
|
#[post(
|
||||||
"/api/w/{workspace_id}/workers/self/workdir-session/operations",
|
"/api/w/{workspace_id}/workers/self/workdir-session/operations",
|
||||||
status = 200,
|
status = 200,
|
||||||
error_status = 400,
|
error_status = 400,
|
||||||
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503],
|
additional_error_statuses = [401, 403, 404, 409, 500, 502, 503],
|
||||||
bearer_auth = true
|
openapi = false,
|
||||||
)]
|
)]
|
||||||
async fn current_worker_workdir_operation(
|
async fn current_worker_workdir_operation(
|
||||||
&self,
|
&self,
|
||||||
@@ -2359,8 +2367,7 @@ pub trait ServerApi {
|
|||||||
#[path] working_directory_id: String,
|
#[path] working_directory_id: String,
|
||||||
#[body] request: WorkingDirectoryRemovalRequest,
|
#[body] request: WorkingDirectoryRemovalRequest,
|
||||||
) -> Result<WorkingDirectoryRemovalResponse, RepositoryApiError>;
|
) -> Result<WorkingDirectoryRemovalResponse, RepositoryApiError>;
|
||||||
|
#[get("/api/tickets", status = 200, error_status = 400, additional_error_statuses = [500], bearer_auth = true, browser_auth = true)]
|
||||||
#[get("/api/tickets", status = 200, error_status = 400, additional_error_statuses = [500])]
|
|
||||||
async fn ticket_list_alias(
|
async fn ticket_list_alias(
|
||||||
&self,
|
&self,
|
||||||
#[query] query: TicketListHttpQuery,
|
#[query] query: TicketListHttpQuery,
|
||||||
@@ -2387,61 +2394,53 @@ pub trait ServerApi {
|
|||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[body] request: TicketQueryRequest,
|
#[body] request: TicketQueryRequest,
|
||||||
) -> Result<TicketQueryResponse, RepositoryApiError>;
|
) -> Result<TicketQueryResponse, RepositoryApiError>;
|
||||||
|
#[get("/api/tickets/{id}", status = 200, error_status = 404, additional_error_statuses = [400, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[get("/api/tickets/{id}", status = 200, error_status = 404, additional_error_statuses = [400, 500])]
|
|
||||||
async fn ticket_get_alias(
|
async fn ticket_get_alias(
|
||||||
&self,
|
&self,
|
||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
) -> Result<TicketDetail, RepositoryApiError>;
|
) -> Result<TicketDetail, RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/default-intake-ready-body", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/default-intake-ready-body", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])]
|
|
||||||
async fn ticket_default_intake_ready_body(
|
async fn ticket_default_intake_ready_body(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[body] request: DefaultIntakeReadyBodyRequest,
|
#[body] request: DefaultIntakeReadyBodyRequest,
|
||||||
) -> Result<TextResponse, RepositoryApiError>;
|
) -> Result<TextResponse, RepositoryApiError>;
|
||||||
|
#[get("/api/w/{workspace_id}/tickets/search", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[get("/api/w/{workspace_id}/tickets/search", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])]
|
|
||||||
async fn ticket_summary_search(
|
async fn ticket_summary_search(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[query] query: TicketSummarySearchQuery,
|
#[query] query: TicketSummarySearchQuery,
|
||||||
) -> Result<TicketRecordSummaryList, RepositoryApiError>;
|
) -> Result<TicketRecordSummaryList, RepositoryApiError>;
|
||||||
|
#[get("/api/w/{workspace_id}/tickets/doctor", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[get("/api/w/{workspace_id}/tickets/doctor", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])]
|
|
||||||
async fn ticket_doctor(
|
async fn ticket_doctor(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
) -> Result<TicketDoctorResponse, RepositoryApiError>;
|
) -> Result<TicketDoctorResponse, RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/relations/search", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/relations/search", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])]
|
|
||||||
async fn ticket_relation_query(
|
async fn ticket_relation_query(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[body] request: TicketRelationSearchRequest,
|
#[body] request: TicketRelationSearchRequest,
|
||||||
) -> Result<TicketRelationRecordList, RepositoryApiError>;
|
) -> Result<TicketRelationRecordList, RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/orchestration-plans/search", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/orchestration-plans/search", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])]
|
|
||||||
async fn ticket_orchestration_plan_query(
|
async fn ticket_orchestration_plan_query(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[body] request: TicketOrchestrationPlanSearchRequest,
|
#[body] request: TicketOrchestrationPlanSearchRequest,
|
||||||
) -> Result<TicketOrchestrationPlanRecordList, RepositoryApiError>;
|
) -> Result<TicketOrchestrationPlanRecordList, RepositoryApiError>;
|
||||||
|
#[get("/api/w/{workspace_id}/tickets/{id}/record", status = 200, error_status = 404, additional_error_statuses = [400, 401, 403, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[get("/api/w/{workspace_id}/tickets/{id}/record", status = 200, error_status = 404, additional_error_statuses = [400, 401, 403, 500])]
|
|
||||||
async fn ticket_record_get(
|
async fn ticket_record_get(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
) -> Result<TicketRecord, RepositoryApiError>;
|
) -> Result<TicketRecord, RepositoryApiError>;
|
||||||
|
#[patch("/api/w/{workspace_id}/tickets/{id}/item", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[patch("/api/w/{workspace_id}/tickets/{id}/item", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn ticket_record_item_edit(
|
async fn ticket_record_item_edit(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -2449,16 +2448,14 @@ pub trait ServerApi {
|
|||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[body] request: EditTicketRecordItemRequest,
|
#[body] request: EditTicketRecordItemRequest,
|
||||||
) -> Result<TicketRecord, RepositoryApiError>;
|
) -> Result<TicketRecord, RepositoryApiError>;
|
||||||
|
#[get("/api/w/{workspace_id}/tickets/{id}/dependency-check", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[get("/api/w/{workspace_id}/tickets/{id}/dependency-check", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])]
|
|
||||||
async fn ticket_dependency_check(
|
async fn ticket_dependency_check(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
) -> Result<TicketDependencyCheckResponse, RepositoryApiError>;
|
) -> Result<TicketDependencyCheckResponse, RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/{id}/thread-events", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/{id}/thread-events", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn ticket_thread_event_add(
|
async fn ticket_thread_event_add(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -2466,8 +2463,7 @@ pub trait ServerApi {
|
|||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[body] request: TicketThreadEventRequest,
|
#[body] request: TicketThreadEventRequest,
|
||||||
) -> Result<(), RepositoryApiError>;
|
) -> Result<(), RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/{id}/state-changes", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/{id}/state-changes", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn ticket_state_change_add(
|
async fn ticket_state_change_add(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -2475,8 +2471,7 @@ pub trait ServerApi {
|
|||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[body] request: TicketStateChangeRequest,
|
#[body] request: TicketStateChangeRequest,
|
||||||
) -> Result<(), RepositoryApiError>;
|
) -> Result<(), RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/{id}/intake-summaries", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/{id}/intake-summaries", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn ticket_intake_summary_add(
|
async fn ticket_intake_summary_add(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -2484,8 +2479,7 @@ pub trait ServerApi {
|
|||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[body] request: TicketIntakeSummaryRequest,
|
#[body] request: TicketIntakeSummaryRequest,
|
||||||
) -> Result<(), RepositoryApiError>;
|
) -> Result<(), RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/{id}/state-fields/{field}", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/{id}/state-fields/{field}", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn ticket_state_field_set(
|
async fn ticket_state_field_set(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -2494,8 +2488,7 @@ pub trait ServerApi {
|
|||||||
#[path] field: String,
|
#[path] field: String,
|
||||||
#[body] request: TicketStateChangeRequest,
|
#[body] request: TicketStateChangeRequest,
|
||||||
) -> Result<(), RepositoryApiError>;
|
) -> Result<(), RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/{id}/workflow-state", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/{id}/workflow-state", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn ticket_workflow_state_set(
|
async fn ticket_workflow_state_set(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -2503,8 +2496,7 @@ pub trait ServerApi {
|
|||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[body] request: TicketStateChangeRequest,
|
#[body] request: TicketStateChangeRequest,
|
||||||
) -> Result<(), RepositoryApiError>;
|
) -> Result<(), RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/{id}/workflow/mark-ready", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/{id}/workflow/mark-ready", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn ticket_mark_ready_record(
|
async fn ticket_mark_ready_record(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -2512,8 +2504,7 @@ pub trait ServerApi {
|
|||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[body] request: TicketMarkReadyRequest,
|
#[body] request: TicketMarkReadyRequest,
|
||||||
) -> Result<TicketRecord, RepositoryApiError>;
|
) -> Result<TicketRecord, RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/{id}/workflow/queue", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/{id}/workflow/queue", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn ticket_queue_record(
|
async fn ticket_queue_record(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -2535,8 +2526,7 @@ pub trait ServerApi {
|
|||||||
#[path] merge_request_id: String,
|
#[path] merge_request_id: String,
|
||||||
#[query] query: MergeRequestThreadQuery,
|
#[query] query: MergeRequestThreadQuery,
|
||||||
) -> Result<MergeRequestDetailResponse, RepositoryApiError>;
|
) -> Result<MergeRequestDetailResponse, RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/{id}/merge-request", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], openapi = false)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/{id}/merge-request", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn merge_request_open(
|
async fn merge_request_open(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -2544,23 +2534,20 @@ pub trait ServerApi {
|
|||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[body] request: OpenMergeRequestRequest,
|
#[body] request: OpenMergeRequestRequest,
|
||||||
) -> Result<PublicMergeRequest, RepositoryApiError>;
|
) -> Result<PublicMergeRequest, RepositoryApiError>;
|
||||||
|
#[get("/api/w/{workspace_id}/tickets/{id}/merge-request/readiness", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[get("/api/w/{workspace_id}/tickets/{id}/merge-request/readiness", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn merge_request_readiness(
|
async fn merge_request_readiness(
|
||||||
&self,
|
&self,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
) -> Result<MergeRequestReadinessResponse, RepositoryApiError>;
|
) -> Result<MergeRequestReadinessResponse, RepositoryApiError>;
|
||||||
|
#[get("/api/w/{workspace_id}/tickets/{id}/merge-request/thread", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[get("/api/w/{workspace_id}/tickets/{id}/merge-request/thread", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])]
|
|
||||||
async fn merge_request_thread(
|
async fn merge_request_thread(
|
||||||
&self,
|
&self,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[query] query: MergeRequestThreadQuery,
|
#[query] query: MergeRequestThreadQuery,
|
||||||
) -> Result<MergeRequestThreadResponse, RepositoryApiError>;
|
) -> Result<MergeRequestThreadResponse, RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/{id}/merge-request/repair-source", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], browser_auth = true)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/{id}/merge-request/repair-source", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)]
|
|
||||||
async fn merge_request_selector_repair(
|
async fn merge_request_selector_repair(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -2568,16 +2555,14 @@ pub trait ServerApi {
|
|||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[body] request: RepairMergeRequestSelectorRequest,
|
#[body] request: RepairMergeRequestSelectorRequest,
|
||||||
) -> Result<PublicMergeRequest, RepositoryApiError>;
|
) -> Result<PublicMergeRequest, RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/internal/reviewer-child-sessions", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], openapi = false)]
|
||||||
#[post("/api/w/{workspace_id}/internal/reviewer-child-sessions", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn merge_request_reviewer_child_register(
|
async fn merge_request_reviewer_child_register(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[body] request: RegisterReviewerChildSessionRequest,
|
#[body] request: RegisterReviewerChildSessionRequest,
|
||||||
) -> Result<(), RepositoryApiError>;
|
) -> Result<(), RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/{id}/merge-request/review-capabilities", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], openapi = false)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/{id}/merge-request/review-capabilities", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn merge_request_review_capability_register(
|
async fn merge_request_review_capability_register(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -2585,16 +2570,14 @@ pub trait ServerApi {
|
|||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[body] request: RegisterMergeRequestReviewCapabilityRequest,
|
#[body] request: RegisterMergeRequestReviewCapabilityRequest,
|
||||||
) -> Result<(), RepositoryApiError>;
|
) -> Result<(), RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/{id}/merge-request/reviews", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], openapi = false)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/{id}/merge-request/reviews", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn merge_request_review_submit(
|
async fn merge_request_review_submit(
|
||||||
&self,
|
&self,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[body] request: SubmitMergeRequestReviewRequest,
|
#[body] request: SubmitMergeRequestReviewRequest,
|
||||||
) -> Result<ReviewEvent, RepositoryApiError>;
|
) -> Result<ReviewEvent, RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/{id}/merge-request/reviews/revoke", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], openapi = false)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/{id}/merge-request/reviews/revoke", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn merge_request_review_revoke(
|
async fn merge_request_review_revoke(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -2602,8 +2585,7 @@ pub trait ServerApi {
|
|||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[body] request: RevokeMergeRequestReviewRequest,
|
#[body] request: RevokeMergeRequestReviewRequest,
|
||||||
) -> Result<ReviewRevokedEvent, RepositoryApiError>;
|
) -> Result<ReviewRevokedEvent, RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/{id}/merge-request/complete", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], openapi = false)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/{id}/merge-request/complete", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn merge_request_complete(
|
async fn merge_request_complete(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -2611,8 +2593,7 @@ pub trait ServerApi {
|
|||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[body] request: CompleteMergeRequestRequest,
|
#[body] request: CompleteMergeRequestRequest,
|
||||||
) -> Result<MergeEvent, RepositoryApiError>;
|
) -> Result<MergeEvent, RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/{id}/workflow/close", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/{id}/workflow/close", status = 204, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn ticket_close_record(
|
async fn ticket_close_record(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -2620,16 +2601,14 @@ pub trait ServerApi {
|
|||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[body] request: TicketCloseRecordRequest,
|
#[body] request: TicketCloseRecordRequest,
|
||||||
) -> Result<(), RepositoryApiError>;
|
) -> Result<(), RepositoryApiError>;
|
||||||
|
#[get("/api/w/{workspace_id}/tickets/{id}/relation-view", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[get("/api/w/{workspace_id}/tickets/{id}/relation-view", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])]
|
|
||||||
async fn ticket_relation_view(
|
async fn ticket_relation_view(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
) -> Result<TicketRelationRecordView, RepositoryApiError>;
|
) -> Result<TicketRelationRecordView, RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/{id}/relations", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/{id}/relations", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn ticket_relation_record(
|
async fn ticket_relation_record(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -2637,8 +2616,7 @@ pub trait ServerApi {
|
|||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[body] request: CreateTicketRelationRequest,
|
#[body] request: CreateTicketRelationRequest,
|
||||||
) -> Result<TicketRelationRecord, RepositoryApiError>;
|
) -> Result<TicketRelationRecord, RepositoryApiError>;
|
||||||
|
#[delete("/api/w/{workspace_id}/tickets/{id}/relations", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[delete("/api/w/{workspace_id}/tickets/{id}/relations", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn ticket_relation_remove(
|
async fn ticket_relation_remove(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -2646,8 +2624,7 @@ pub trait ServerApi {
|
|||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[body] request: TicketRelationRemoveRequest,
|
#[body] request: TicketRelationRemoveRequest,
|
||||||
) -> Result<TicketRelationRecord, RepositoryApiError>;
|
) -> Result<TicketRelationRecord, RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/{id}/orchestration-plans", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/{id}/orchestration-plans", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 500])]
|
|
||||||
async fn ticket_orchestration_plan_record(
|
async fn ticket_orchestration_plan_record(
|
||||||
&self,
|
&self,
|
||||||
#[extension] context: ServerRequestContext,
|
#[extension] context: ServerRequestContext,
|
||||||
@@ -2670,8 +2647,7 @@ pub trait ServerApi {
|
|||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[body] request: BrowserEditTicketRequest,
|
#[body] request: BrowserEditTicketRequest,
|
||||||
) -> Result<TicketDetail, RepositoryApiError>;
|
) -> Result<TicketDetail, RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/tickets/{id}/show", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[post("/api/w/{workspace_id}/tickets/{id}/show", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])]
|
|
||||||
async fn ticket_show(
|
async fn ticket_show(
|
||||||
&self,
|
&self,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
@@ -2751,8 +2727,7 @@ pub trait ServerApi {
|
|||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
#[body] request: BrowserCloseTicketRequest,
|
#[body] request: BrowserCloseTicketRequest,
|
||||||
) -> Result<TicketDetail, RepositoryApiError>;
|
) -> Result<TicketDetail, RepositoryApiError>;
|
||||||
|
#[get("/api/objectives", status = 200, error_status = 400, additional_error_statuses = [500], bearer_auth = true, browser_auth = true)]
|
||||||
#[get("/api/objectives", status = 200, error_status = 400, additional_error_statuses = [500])]
|
|
||||||
async fn objective_list_alias(
|
async fn objective_list_alias(
|
||||||
&self,
|
&self,
|
||||||
#[query] query: ObjectiveListQuery,
|
#[query] query: ObjectiveListQuery,
|
||||||
@@ -2771,15 +2746,13 @@ pub trait ServerApi {
|
|||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[body] request: ObjectiveCreateRequest,
|
#[body] request: ObjectiveCreateRequest,
|
||||||
) -> Result<ObjectiveDetail, RepositoryApiError>;
|
) -> Result<ObjectiveDetail, RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/objectives/query", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[post("/api/w/{workspace_id}/objectives/query", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])]
|
|
||||||
async fn objective_query(
|
async fn objective_query(
|
||||||
&self,
|
&self,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
#[body] request: ObjectiveQueryRequest,
|
#[body] request: ObjectiveQueryRequest,
|
||||||
) -> Result<ObjectiveQueryResponse, RepositoryApiError>;
|
) -> Result<ObjectiveQueryResponse, RepositoryApiError>;
|
||||||
|
#[get("/api/objectives/{id}", status = 200, error_status = 404, additional_error_statuses = [400, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[get("/api/objectives/{id}", status = 200, error_status = 404, additional_error_statuses = [400, 500])]
|
|
||||||
async fn objective_get_alias(
|
async fn objective_get_alias(
|
||||||
&self,
|
&self,
|
||||||
#[path] id: String,
|
#[path] id: String,
|
||||||
@@ -2799,8 +2772,7 @@ pub trait ServerApi {
|
|||||||
#[path] objective_id: String,
|
#[path] objective_id: String,
|
||||||
#[body] request: ObjectiveEditRequest,
|
#[body] request: ObjectiveEditRequest,
|
||||||
) -> Result<ObjectiveDetail, RepositoryApiError>;
|
) -> Result<ObjectiveDetail, RepositoryApiError>;
|
||||||
|
#[post("/api/w/{workspace_id}/objectives/{objective_id}/show", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
|
||||||
#[post("/api/w/{workspace_id}/objectives/{objective_id}/show", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500])]
|
|
||||||
async fn objective_show(
|
async fn objective_show(
|
||||||
&self,
|
&self,
|
||||||
#[path] workspace_id: String,
|
#[path] workspace_id: String,
|
||||||
@@ -10555,6 +10527,130 @@ mod openapi_artifact_tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn server_api_openapi_security_matches_workspace_server_auth_semantics() {
|
||||||
|
const PUBLIC_UNAUTHENTICATED: &[&str] = &[
|
||||||
|
"health",
|
||||||
|
"auth_config",
|
||||||
|
"auth_bootstrap_user",
|
||||||
|
"auth_passkey_registration_options",
|
||||||
|
"auth_passkey_login_options",
|
||||||
|
"auth_device_login_start",
|
||||||
|
"auth_device_login_poll",
|
||||||
|
"auth_whoami",
|
||||||
|
];
|
||||||
|
// These operations are authenticated by Runtime request-source proof, Worker source
|
||||||
|
// identity, or a one-use Reviewer capability. They remain generated Rust client/Axum
|
||||||
|
// operations, but must not appear as unauthenticated operations in the public OpenAPI.
|
||||||
|
const SIGNED_INTERNAL: &[&str] = &[
|
||||||
|
"current_worker_workdir_attach",
|
||||||
|
"current_worker_workdir_detach",
|
||||||
|
"current_worker_workdir_operation",
|
||||||
|
"merge_request_complete",
|
||||||
|
"merge_request_open",
|
||||||
|
"merge_request_review_capability_register",
|
||||||
|
"merge_request_review_revoke",
|
||||||
|
"merge_request_review_submit",
|
||||||
|
"merge_request_reviewer_child_register",
|
||||||
|
"runtime_resource_fetch",
|
||||||
|
"worker_control_cancel",
|
||||||
|
"worker_control_input",
|
||||||
|
"worker_control_list",
|
||||||
|
"worker_control_restore",
|
||||||
|
"worker_control_spawn",
|
||||||
|
"worker_control_stop",
|
||||||
|
"worker_observation_capture",
|
||||||
|
"worker_observation_sessions",
|
||||||
|
"workspace_worker_discovery",
|
||||||
|
"workspace_worker_remove",
|
||||||
|
];
|
||||||
|
// Session observation retains its existing explicit non-OpenAPI boundary independently of
|
||||||
|
// the Runtime/Worker-source signed operation inventory above.
|
||||||
|
const OTHER_OPENAPI_EXCLUDED: &[&str] = &["worker_session"];
|
||||||
|
const BROWSER_ONLY: &[&str] = &["merge_request_selector_repair"];
|
||||||
|
|
||||||
|
let document = canonical_openapi_document().expect("canonical OpenAPI contract must build");
|
||||||
|
let value: serde_json::Value =
|
||||||
|
serde_json::from_str(&document.to_json().expect("document must serialize"))
|
||||||
|
.expect("document must be JSON");
|
||||||
|
let paths = value["paths"]
|
||||||
|
.as_object()
|
||||||
|
.expect("OpenAPI paths must exist");
|
||||||
|
let mut documented = std::collections::BTreeMap::new();
|
||||||
|
for path in paths.values() {
|
||||||
|
for operation in path
|
||||||
|
.as_object()
|
||||||
|
.expect("OpenAPI path item must be an object")
|
||||||
|
.values()
|
||||||
|
{
|
||||||
|
let operation_id = operation["operationId"]
|
||||||
|
.as_str()
|
||||||
|
.expect("OpenAPI operationId must be a string");
|
||||||
|
assert!(
|
||||||
|
documented.insert(operation_id, operation).is_none(),
|
||||||
|
"duplicate OpenAPI operationId {operation_id}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let operations = ServerApiMetadata::OPERATIONS;
|
||||||
|
assert_eq!(
|
||||||
|
documented.len() + SIGNED_INTERNAL.len() + OTHER_OPENAPI_EXCLUDED.len(),
|
||||||
|
operations.len(),
|
||||||
|
"every ServerApi operation must have one bounded OpenAPI auth disposition"
|
||||||
|
);
|
||||||
|
for operation in operations {
|
||||||
|
let operation_id = operation.operation_id;
|
||||||
|
if SIGNED_INTERNAL.contains(&operation_id)
|
||||||
|
|| OTHER_OPENAPI_EXCLUDED.contains(&operation_id)
|
||||||
|
{
|
||||||
|
assert!(
|
||||||
|
!documented.contains_key(operation_id),
|
||||||
|
"internal/excluded operation {operation_id} must not appear public in OpenAPI"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let documented_operation = documented
|
||||||
|
.get(operation_id)
|
||||||
|
.unwrap_or_else(|| panic!("missing public OpenAPI operation {operation_id}"));
|
||||||
|
let expected = if PUBLIC_UNAUTHENTICATED.contains(&operation_id) {
|
||||||
|
&[][..]
|
||||||
|
} else if BROWSER_ONLY.contains(&operation_id) {
|
||||||
|
&["browserSession"][..]
|
||||||
|
} else {
|
||||||
|
&["bearerAuth", "browserSession"][..]
|
||||||
|
};
|
||||||
|
let security = documented_operation.get("security");
|
||||||
|
if expected.is_empty() {
|
||||||
|
assert!(
|
||||||
|
security.is_none(),
|
||||||
|
"public unauthenticated operation {operation_id} must not require security"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let security = security
|
||||||
|
.and_then(serde_json::Value::as_array)
|
||||||
|
.unwrap_or_else(|| panic!("authenticated operation {operation_id} lacks security"));
|
||||||
|
let mut actual = security
|
||||||
|
.iter()
|
||||||
|
.map(|requirement| {
|
||||||
|
let requirement = requirement.as_object().unwrap_or_else(|| {
|
||||||
|
panic!("security requirement for {operation_id} must be an object")
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
requirement.len(),
|
||||||
|
1,
|
||||||
|
"security alternatives for {operation_id} must remain independent OR choices"
|
||||||
|
);
|
||||||
|
requirement.keys().next().expect("one security scheme").as_str()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
actual.sort_unstable();
|
||||||
|
assert_eq!(actual, expected, "security mismatch for {operation_id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn auth_and_workspace_catalog_contract_is_strict_and_excludes_cookie_routes() {
|
fn auth_and_workspace_catalog_contract_is_strict_and_excludes_cookie_routes() {
|
||||||
let document = canonical_openapi_document().expect("canonical OpenAPI contract must build");
|
let document = canonical_openapi_document().expect("canonical OpenAPI contract must build");
|
||||||
|
|||||||
@@ -662,27 +662,6 @@ fn validate_workdir_operations(
|
|||||||
request: Some("WorkingDirectoryRemovalRequest"),
|
request: Some("WorkingDirectoryRemovalRequest"),
|
||||||
successes: &[("200", "WorkingDirectoryRemovalResponse")],
|
successes: &[("200", "WorkingDirectoryRemovalResponse")],
|
||||||
},
|
},
|
||||||
OperationContract {
|
|
||||||
path: "/api/w/{workspace_id}/workers/self/workdir-attachments",
|
|
||||||
method: "post",
|
|
||||||
operation_id: "current_worker_workdir_attach",
|
|
||||||
request: Some("CurrentWorkerWorkdirAttachRequest"),
|
|
||||||
successes: &[("200", "CurrentWorkerWorkdirAttachmentResponse")],
|
|
||||||
},
|
|
||||||
OperationContract {
|
|
||||||
path: "/api/w/{workspace_id}/workers/self/workdir-attachments/{alias}",
|
|
||||||
method: "delete",
|
|
||||||
operation_id: "current_worker_workdir_detach",
|
|
||||||
request: None,
|
|
||||||
successes: &[("200", "CurrentWorkerWorkdirAttachmentResponse")],
|
|
||||||
},
|
|
||||||
OperationContract {
|
|
||||||
path: "/api/w/{workspace_id}/workers/self/workdir-session/operations",
|
|
||||||
method: "post",
|
|
||||||
operation_id: "current_worker_workdir_operation",
|
|
||||||
request: Some("CurrentWorkerWorkdirOperationRequest"),
|
|
||||||
successes: &[("200", "WorkdirSessionOperationResult")],
|
|
||||||
},
|
|
||||||
OperationContract {
|
OperationContract {
|
||||||
path: "/api/w/{workspace_id}/external-workdir-grants",
|
path: "/api/w/{workspace_id}/external-workdir-grants",
|
||||||
method: "post",
|
method: "post",
|
||||||
@@ -1140,13 +1119,10 @@ fn validate_operation_contracts(
|
|||||||
&format!("{} response {status}", operation.operation_id),
|
&format!("{} response {status}", operation.operation_id),
|
||||||
)?;
|
)?;
|
||||||
let schema = json_content_schema(response, operation.operation_id)?;
|
let schema = json_content_schema(response, operation.operation_id)?;
|
||||||
let expected = successes.get(status.as_str()).copied().unwrap_or_else(|| {
|
let expected = successes
|
||||||
if operation.operation_id == "current_worker_workdir_operation" {
|
.get(status.as_str())
|
||||||
"WorkdirOperationApiError"
|
.copied()
|
||||||
} else {
|
.unwrap_or("RepositoryApiError");
|
||||||
"RepositoryApiError"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
require_schema_ref(schema, expected, operation.operation_id)?;
|
require_schema_ref(schema, expected, operation.operation_id)?;
|
||||||
roots.insert(expected.to_owned());
|
roots.insert(expected.to_owned());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ use crate::records::{
|
|||||||
ObjectiveQueryItem, ObjectiveQueryRequest, ObjectiveQueryResponse, ObjectiveResourceSummary,
|
ObjectiveQueryItem, ObjectiveQueryRequest, ObjectiveQueryResponse, ObjectiveResourceSummary,
|
||||||
ObjectiveShowRequest, ObjectiveSummary, ProjectRecordList, QueryPage, TicketActionEligibility,
|
ObjectiveShowRequest, ObjectiveSummary, ProjectRecordList, QueryPage, TicketActionEligibility,
|
||||||
TicketAssignmentPrincipalSummary, TicketAssignmentSummary, TicketDetail, TicketEventDetail,
|
TicketAssignmentPrincipalSummary, TicketAssignmentSummary, TicketDetail, TicketEventDetail,
|
||||||
TicketEvidenceEvent, TicketEvidenceSummary, TicketListPageRequest, TicketMergeRequestSummary,
|
TicketEvidenceEvent, TicketEvidenceSummary, TicketListProjectionRequest,
|
||||||
TicketQueryItem, TicketQueryRequest, TicketQueryResponse, TicketRelationView,
|
TicketMergeRequestSummary, TicketQueryItem, TicketQueryRequest, TicketQueryResponse,
|
||||||
TicketRoleAssignmentSummary, TicketShowRequest, TicketSummary, TicketSummaryPage,
|
TicketRoleAssignmentSummary, TicketShowRequest, TicketSummary, TicketSummaryPage,
|
||||||
summarize_body, truncate_body,
|
summarize_body, ticket_relation_view_from_domain, truncate_body,
|
||||||
};
|
};
|
||||||
use crate::store::{
|
use crate::store::{
|
||||||
ControlPlaneStore, MemoryDocumentRecord, MemoryStagingRecord, MemoryStagingResolutionRecord,
|
ControlPlaneStore, MemoryDocumentRecord, MemoryStagingRecord, MemoryStagingResolutionRecord,
|
||||||
@@ -48,7 +48,7 @@ impl<T> WorkspaceAuthority for T where T: ObjectiveAuthority + TicketAuthority +
|
|||||||
|
|
||||||
pub trait TicketAuthority {
|
pub trait TicketAuthority {
|
||||||
fn list_tickets(&self, limit: usize) -> Result<ProjectRecordList<TicketSummary>>;
|
fn list_tickets(&self, limit: usize) -> Result<ProjectRecordList<TicketSummary>>;
|
||||||
fn list_ticket_page(&self, request: TicketListPageRequest) -> Result<TicketSummaryPage>;
|
fn list_ticket_page(&self, request: TicketListProjectionRequest) -> Result<TicketSummaryPage>;
|
||||||
fn query_tickets(&self, query: TicketQueryRequest) -> Result<TicketQueryResponse>;
|
fn query_tickets(&self, query: TicketQueryRequest) -> Result<TicketQueryResponse>;
|
||||||
fn ticket(&self, id: &str) -> Result<TicketDetail>;
|
fn ticket(&self, id: &str) -> Result<TicketDetail>;
|
||||||
fn show_ticket(&self, id: &str, query: TicketShowRequest) -> Result<TicketDetail>;
|
fn show_ticket(&self, id: &str, query: TicketShowRequest) -> Result<TicketDetail>;
|
||||||
@@ -86,14 +86,14 @@ pub trait MemoryAuthority {
|
|||||||
fn ensure_memory_document(&self) -> Result<MemoryDocument>;
|
fn ensure_memory_document(&self) -> Result<MemoryDocument>;
|
||||||
fn memory_document(&self) -> Result<MemoryDocument>;
|
fn memory_document(&self) -> Result<MemoryDocument>;
|
||||||
fn update_memory_document(&self, body_md: &str) -> Result<MemoryDocument>;
|
fn update_memory_document(&self, body_md: &str) -> Result<MemoryDocument>;
|
||||||
fn list_memory_staging_records(&self, limit: usize) -> Result<Vec<MemoryStagingEntry>>;
|
fn list_memory_staging_records(&self, limit: usize) -> Result<Vec<InternalMemoryStagingEntry>>;
|
||||||
fn memory_staging_record(&self, candidate_id: &str) -> Result<MemoryStagingEntry>;
|
fn memory_staging_record(&self, candidate_id: &str) -> Result<InternalMemoryStagingEntry>;
|
||||||
fn upsert_memory_staging_record(
|
fn upsert_memory_staging_record(
|
||||||
&self,
|
&self,
|
||||||
candidate_id: &str,
|
candidate_id: &str,
|
||||||
raw_json: &str,
|
raw_json: &str,
|
||||||
source_path: Option<&str>,
|
source_path: Option<&str>,
|
||||||
) -> Result<MemoryStagingEntry>;
|
) -> Result<InternalMemoryStagingEntry>;
|
||||||
fn close_memory_staging_record(
|
fn close_memory_staging_record(
|
||||||
&self,
|
&self,
|
||||||
candidate_id: &str,
|
candidate_id: &str,
|
||||||
@@ -114,7 +114,7 @@ pub struct MemoryDocument {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct MemoryStagingEntry {
|
pub struct InternalMemoryStagingEntry {
|
||||||
pub candidate_id: String,
|
pub candidate_id: String,
|
||||||
pub raw_json: String,
|
pub raw_json: String,
|
||||||
pub source_path: Option<String>,
|
pub source_path: Option<String>,
|
||||||
@@ -966,7 +966,7 @@ impl SqliteWorkspaceAuthority {
|
|||||||
&ticket.meta.id,
|
&ticket.meta.id,
|
||||||
)?)
|
)?)
|
||||||
.ok_or_else(|| Error::Store(format!("missing resource key for {}", ticket.meta.id)))?;
|
.ok_or_else(|| Error::Store(format!("missing resource key for {}", ticket.meta.id)))?;
|
||||||
let mut relations: TicketRelationView = ticket.relations.into();
|
let mut relations = ticket_relation_view_from_domain(ticket.relations);
|
||||||
for relation in &mut relations.outgoing {
|
for relation in &mut relations.outgoing {
|
||||||
relation.target_resource_key = self.store.resource_key(
|
relation.target_resource_key = self.store.resource_key(
|
||||||
&self.workspace_id,
|
&self.workspace_id,
|
||||||
@@ -1073,7 +1073,7 @@ impl TicketAuthority for SqliteWorkspaceAuthority {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_ticket_page(&self, request: TicketListPageRequest) -> Result<TicketSummaryPage> {
|
fn list_ticket_page(&self, request: TicketListProjectionRequest) -> Result<TicketSummaryPage> {
|
||||||
let limit = request.limit.unwrap_or(30).clamp(1, 100);
|
let limit = request.limit.unwrap_or(30).clamp(1, 100);
|
||||||
let mut states = request.states;
|
let mut states = request.states;
|
||||||
states.sort();
|
states.sort();
|
||||||
@@ -1587,7 +1587,7 @@ impl MemoryAuthority for SqliteWorkspaceAuthority {
|
|||||||
Ok(memory_document_from_record(record))
|
Ok(memory_document_from_record(record))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_memory_staging_records(&self, limit: usize) -> Result<Vec<MemoryStagingEntry>> {
|
fn list_memory_staging_records(&self, limit: usize) -> Result<Vec<InternalMemoryStagingEntry>> {
|
||||||
self.store
|
self.store
|
||||||
.list_memory_staging_records(&self.workspace_id, limit)
|
.list_memory_staging_records(&self.workspace_id, limit)
|
||||||
.map(|records| {
|
.map(|records| {
|
||||||
@@ -1598,7 +1598,7 @@ impl MemoryAuthority for SqliteWorkspaceAuthority {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn memory_staging_record(&self, candidate_id: &str) -> Result<MemoryStagingEntry> {
|
fn memory_staging_record(&self, candidate_id: &str) -> Result<InternalMemoryStagingEntry> {
|
||||||
validate_memory_candidate_id(candidate_id)?;
|
validate_memory_candidate_id(candidate_id)?;
|
||||||
self.store
|
self.store
|
||||||
.get_memory_staging_record(&self.workspace_id, candidate_id)?
|
.get_memory_staging_record(&self.workspace_id, candidate_id)?
|
||||||
@@ -1613,7 +1613,7 @@ impl MemoryAuthority for SqliteWorkspaceAuthority {
|
|||||||
candidate_id: &str,
|
candidate_id: &str,
|
||||||
raw_json: &str,
|
raw_json: &str,
|
||||||
source_path: Option<&str>,
|
source_path: Option<&str>,
|
||||||
) -> Result<MemoryStagingEntry> {
|
) -> Result<InternalMemoryStagingEntry> {
|
||||||
validate_memory_candidate_id(candidate_id)?;
|
validate_memory_candidate_id(candidate_id)?;
|
||||||
validate_json_object(raw_json, "raw_json")?;
|
validate_json_object(raw_json, "raw_json")?;
|
||||||
let imported_at = now_rfc3339();
|
let imported_at = now_rfc3339();
|
||||||
@@ -2634,8 +2634,8 @@ fn memory_document_from_record(record: MemoryDocumentRecord) -> MemoryDocument {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn memory_staging_from_record(record: MemoryStagingRecord) -> MemoryStagingEntry {
|
fn memory_staging_from_record(record: MemoryStagingRecord) -> InternalMemoryStagingEntry {
|
||||||
MemoryStagingEntry {
|
InternalMemoryStagingEntry {
|
||||||
candidate_id: record.candidate_id,
|
candidate_id: record.candidate_id,
|
||||||
raw_json: record.raw_json,
|
raw_json: record.raw_json,
|
||||||
source_path: record.source_path,
|
source_path: record.source_path,
|
||||||
@@ -3330,7 +3330,7 @@ VALUES ('workspace-test', 'ticket', 4);
|
|||||||
assert_eq!(incoming_relation.items.len(), 1);
|
assert_eq!(incoming_relation.items.len(), 1);
|
||||||
assert_eq!(incoming_relation.items[0].id, "00000000001J5");
|
assert_eq!(incoming_relation.items[0].id, "00000000001J5");
|
||||||
let summary_page = authority
|
let summary_page = authority
|
||||||
.list_ticket_page(TicketListPageRequest {
|
.list_ticket_page(TicketListProjectionRequest {
|
||||||
states: vec!["planning".to_string(), "ready".to_string()],
|
states: vec!["planning".to_string(), "ready".to_string()],
|
||||||
limit: Some(1),
|
limit: Some(1),
|
||||||
cursor: None,
|
cursor: None,
|
||||||
@@ -3338,7 +3338,7 @@ VALUES ('workspace-test', 'ticket', 4);
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(summary_page.items.len(), 1);
|
assert_eq!(summary_page.items.len(), 1);
|
||||||
assert!(summary_page.page.has_more);
|
assert!(summary_page.page.has_more);
|
||||||
let mismatched_summary_cursor = authority.list_ticket_page(TicketListPageRequest {
|
let mismatched_summary_cursor = authority.list_ticket_page(TicketListProjectionRequest {
|
||||||
states: vec!["done".to_string()],
|
states: vec!["done".to_string()],
|
||||||
limit: Some(1),
|
limit: Some(1),
|
||||||
cursor: summary_page.page.next_cursor,
|
cursor: summary_page.page.next_cursor,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -36,7 +36,7 @@ pub mod workspace_signing_identity;
|
|||||||
mod workspace_subscription;
|
mod workspace_subscription;
|
||||||
|
|
||||||
pub use authority::{
|
pub use authority::{
|
||||||
MemoryAuthority, MemoryDocument, MemoryStagingEntry, MemoryStagingResolution,
|
InternalMemoryStagingEntry, MemoryAuthority, MemoryDocument, MemoryStagingResolution,
|
||||||
ObjectiveAuthority, SqliteWorkspaceAuthority, TicketAuthority, TicketMergeRevisionSource,
|
ObjectiveAuthority, SqliteWorkspaceAuthority, TicketAuthority, TicketMergeRevisionSource,
|
||||||
WorkspaceAuthority,
|
WorkspaceAuthority,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,29 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
pub use server_api::{
|
pub use server_api::{
|
||||||
ObjectiveDetail, ObjectiveEventDetail, ObjectiveLinkedTicketSummary, ObjectiveResourceSummary,
|
InvalidProjectRecord, MergeRequestListItem, MergeRequestListResponse,
|
||||||
ObjectiveSummary, QueryPage,
|
MergeRequestRefDiagnostic, ObjectiveDetail, ObjectiveEventDetail, ObjectiveLinkSummary,
|
||||||
|
ObjectiveLinkedTicketSummary, ObjectiveQueryItem, ObjectiveQueryRequest,
|
||||||
|
ObjectiveQueryResponse, ObjectiveResourceSummary, ObjectiveShowRequest, ObjectiveSummary,
|
||||||
|
QueryPage, TicketActionEligibility,
|
||||||
|
TicketAssignmentPrincipal as TicketAssignmentPrincipalSummary, TicketAssignmentSummary,
|
||||||
|
TicketDetail, TicketDetailDerivedRelation as DerivedTicketRelation,
|
||||||
|
TicketDetailRelation as TicketRelation, TicketDetailRelationBlocker as TicketRelationBlocker,
|
||||||
|
TicketDetailRelationNotice as TicketRelationNotice,
|
||||||
|
TicketDetailRelationView as TicketRelationView, TicketEventDetail, TicketEvidenceEvent,
|
||||||
|
TicketEvidenceSummary, TicketListItemSummary as TicketSummary, TicketListResponse,
|
||||||
|
TicketMergeRequestSummary, TicketQueryItem, TicketQueryRequest, TicketQueryResponse,
|
||||||
|
TicketRoleAssignmentSummary, TicketShowRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SUMMARY_BODY_LIMIT: usize = 240;
|
const SUMMARY_BODY_LIMIT: usize = 240;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct ProjectRecordList<T> {
|
pub struct ProjectRecordList<T> {
|
||||||
pub items: Vec<T>,
|
pub items: Vec<T>,
|
||||||
pub invalid_records: Vec<InvalidProjectRecord>,
|
pub invalid_records: Vec<InvalidProjectRecord>,
|
||||||
pub record_authority: String,
|
pub record_authority: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct TicketSummaryPage {
|
pub struct TicketSummaryPage {
|
||||||
pub items: Vec<TicketSummary>,
|
pub items: Vec<TicketSummary>,
|
||||||
pub page: QueryPage,
|
pub page: QueryPage,
|
||||||
@@ -21,418 +31,67 @@ pub struct TicketSummaryPage {
|
|||||||
pub record_authority: String,
|
pub record_authority: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
pub struct InvalidProjectRecord {
|
pub struct TicketListProjectionRequest {
|
||||||
pub label: String,
|
|
||||||
pub reason: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct TicketSummary {
|
|
||||||
pub id: String,
|
|
||||||
pub resource_key: String,
|
|
||||||
pub title: String,
|
|
||||||
pub state: String,
|
|
||||||
pub priority: String,
|
|
||||||
pub updated_at: Option<String>,
|
|
||||||
pub queued_by: Option<String>,
|
|
||||||
pub queued_at: Option<String>,
|
|
||||||
pub workspace_action_priority: String,
|
|
||||||
pub record_source: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
|
|
||||||
pub struct TicketListPageRequest {
|
|
||||||
#[serde(default)]
|
|
||||||
pub states: Vec<String>,
|
pub states: Vec<String>,
|
||||||
#[serde(default)]
|
|
||||||
pub limit: Option<usize>,
|
pub limit: Option<usize>,
|
||||||
#[serde(default)]
|
|
||||||
pub cursor: Option<String>,
|
pub cursor: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
pub(crate) fn ticket_relation_view_from_domain(
|
||||||
pub struct TicketListResponse {
|
value: ticket::TicketRelationView,
|
||||||
pub workspace_id: String,
|
) -> TicketRelationView {
|
||||||
pub limit: usize,
|
TicketRelationView {
|
||||||
pub items: Vec<TicketSummary>,
|
outgoing: value
|
||||||
pub page: QueryPage,
|
.outgoing
|
||||||
pub invalid_records: Vec<InvalidProjectRecord>,
|
.into_iter()
|
||||||
pub record_authority: String,
|
.map(|relation| TicketRelation {
|
||||||
}
|
ticket_id: relation.ticket_id,
|
||||||
|
kind: relation.kind.as_str().to_string(),
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
target: relation.target,
|
||||||
pub struct TicketDetail {
|
target_resource_key: None,
|
||||||
pub id: String,
|
note: relation.note,
|
||||||
pub resource_key: String,
|
author: relation.author,
|
||||||
pub title: String,
|
at: relation.at,
|
||||||
pub state: String,
|
})
|
||||||
pub readiness: Option<String>,
|
.collect(),
|
||||||
pub priority: String,
|
incoming: value
|
||||||
pub created_at: Option<String>,
|
.incoming
|
||||||
pub updated_at: Option<String>,
|
.into_iter()
|
||||||
pub item_revision: String,
|
.map(|relation| DerivedTicketRelation {
|
||||||
pub queued_by: Option<String>,
|
source_ticket: relation.source_ticket,
|
||||||
pub queued_at: Option<String>,
|
source_resource_key: None,
|
||||||
pub repository_key: Option<String>,
|
inverse_kind: relation.inverse_kind,
|
||||||
pub ref_selector: Option<String>,
|
forward_kind: relation.forward_kind.as_str().to_string(),
|
||||||
pub risk_flags: Vec<String>,
|
note: relation.note,
|
||||||
pub body: String,
|
author: relation.author,
|
||||||
pub body_truncated: bool,
|
at: relation.at,
|
||||||
pub event_count: usize,
|
})
|
||||||
pub events: Vec<TicketEventDetail>,
|
.collect(),
|
||||||
pub event_page: QueryPage,
|
blockers: value
|
||||||
pub artifact_count: usize,
|
.blockers
|
||||||
pub artifacts: Vec<String>,
|
.into_iter()
|
||||||
pub relations: TicketRelationView,
|
.map(|blocker| TicketRelationBlocker {
|
||||||
pub linked_objectives: Vec<ObjectiveLinkSummary>,
|
blocking_ticket: blocker.blocking_ticket,
|
||||||
pub implementation_reports: Vec<TicketEvidenceEvent>,
|
blocking_resource_key: None,
|
||||||
pub assignments: Vec<TicketRoleAssignmentSummary>,
|
reason_kind: blocker.reason_kind,
|
||||||
pub current_coder: Option<TicketAssignmentSummary>,
|
relation_kind: blocker.relation_kind.as_str().to_string(),
|
||||||
pub assignment_diagnostics: Vec<String>,
|
note: blocker.note,
|
||||||
pub action_eligibility: TicketActionEligibility,
|
blocking_state: blocker.blocking_state.as_str().to_string(),
|
||||||
pub merge_request: Option<TicketMergeRequestSummary>,
|
})
|
||||||
pub evidence: TicketEvidenceSummary,
|
.collect(),
|
||||||
pub resolution: Option<String>,
|
notices: value
|
||||||
pub record_source: String,
|
.notices
|
||||||
}
|
.into_iter()
|
||||||
|
.map(|notice| TicketRelationNotice {
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
related_ticket: notice.related_ticket,
|
||||||
pub struct TicketEventDetail {
|
kind: notice.kind.as_str().to_string(),
|
||||||
pub sequence: usize,
|
message: notice.message,
|
||||||
pub event_ref: String,
|
})
|
||||||
pub kind: String,
|
.collect(),
|
||||||
pub author: Option<String>,
|
|
||||||
pub at: Option<String>,
|
|
||||||
pub status: Option<String>,
|
|
||||||
pub from: Option<String>,
|
|
||||||
pub to: Option<String>,
|
|
||||||
pub reason: Option<String>,
|
|
||||||
pub state_field: Option<String>,
|
|
||||||
pub heading: Option<String>,
|
|
||||||
pub body: Option<String>,
|
|
||||||
pub attributes: std::collections::BTreeMap<String, String>,
|
|
||||||
pub references: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct TicketRelation {
|
|
||||||
pub ticket_id: String,
|
|
||||||
pub kind: String,
|
|
||||||
pub target: String,
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub target_resource_key: Option<String>,
|
|
||||||
pub note: Option<String>,
|
|
||||||
pub author: String,
|
|
||||||
pub at: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct DerivedTicketRelation {
|
|
||||||
pub source_ticket: String,
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub source_resource_key: Option<String>,
|
|
||||||
pub inverse_kind: String,
|
|
||||||
pub forward_kind: String,
|
|
||||||
pub note: Option<String>,
|
|
||||||
pub author: String,
|
|
||||||
pub at: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct TicketRelationBlocker {
|
|
||||||
pub blocking_ticket: String,
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub blocking_resource_key: Option<String>,
|
|
||||||
pub reason_kind: String,
|
|
||||||
pub relation_kind: String,
|
|
||||||
pub note: Option<String>,
|
|
||||||
pub blocking_state: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct TicketRelationNotice {
|
|
||||||
pub related_ticket: String,
|
|
||||||
pub kind: String,
|
|
||||||
pub message: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct TicketRelationView {
|
|
||||||
pub outgoing: Vec<TicketRelation>,
|
|
||||||
pub incoming: Vec<DerivedTicketRelation>,
|
|
||||||
pub blockers: Vec<TicketRelationBlocker>,
|
|
||||||
pub notices: Vec<TicketRelationNotice>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ticket::TicketRelationView> for TicketRelationView {
|
|
||||||
fn from(value: ticket::TicketRelationView) -> Self {
|
|
||||||
Self {
|
|
||||||
outgoing: value
|
|
||||||
.outgoing
|
|
||||||
.into_iter()
|
|
||||||
.map(|relation| TicketRelation {
|
|
||||||
ticket_id: relation.ticket_id,
|
|
||||||
kind: relation.kind.as_str().to_string(),
|
|
||||||
target: relation.target,
|
|
||||||
target_resource_key: None,
|
|
||||||
note: relation.note,
|
|
||||||
author: relation.author,
|
|
||||||
at: relation.at,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
incoming: value
|
|
||||||
.incoming
|
|
||||||
.into_iter()
|
|
||||||
.map(|relation| DerivedTicketRelation {
|
|
||||||
source_ticket: relation.source_ticket,
|
|
||||||
source_resource_key: None,
|
|
||||||
inverse_kind: relation.inverse_kind,
|
|
||||||
forward_kind: relation.forward_kind.as_str().to_string(),
|
|
||||||
note: relation.note,
|
|
||||||
author: relation.author,
|
|
||||||
at: relation.at,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
blockers: value
|
|
||||||
.blockers
|
|
||||||
.into_iter()
|
|
||||||
.map(|blocker| TicketRelationBlocker {
|
|
||||||
blocking_ticket: blocker.blocking_ticket,
|
|
||||||
blocking_resource_key: None,
|
|
||||||
reason_kind: blocker.reason_kind,
|
|
||||||
relation_kind: blocker.relation_kind.as_str().to_string(),
|
|
||||||
note: blocker.note,
|
|
||||||
blocking_state: blocker.blocking_state.as_str().to_string(),
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
notices: value
|
|
||||||
.notices
|
|
||||||
.into_iter()
|
|
||||||
.map(|notice| TicketRelationNotice {
|
|
||||||
related_ticket: notice.related_ticket,
|
|
||||||
kind: notice.kind.as_str().to_string(),
|
|
||||||
message: notice.message,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct ObjectiveLinkSummary {
|
|
||||||
pub id: String,
|
|
||||||
pub resource_key: String,
|
|
||||||
pub title: String,
|
|
||||||
pub state: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct TicketEvidenceEvent {
|
|
||||||
pub event_ref: String,
|
|
||||||
pub sequence: usize,
|
|
||||||
pub kind: String,
|
|
||||||
pub at: Option<String>,
|
|
||||||
pub author: Option<String>,
|
|
||||||
pub excerpt: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct TicketAssignmentSummary {
|
|
||||||
pub assignment_id: String,
|
|
||||||
pub runtime_id: String,
|
|
||||||
pub worker_id: String,
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub worker_resource_key: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct TicketRoleAssignmentSummary {
|
|
||||||
pub assignment_id: String,
|
|
||||||
pub role: String,
|
|
||||||
pub principal: TicketAssignmentPrincipalSummary,
|
|
||||||
pub assigned_by: String,
|
|
||||||
pub assigned_at: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
|
||||||
pub enum TicketAssignmentPrincipalSummary {
|
|
||||||
User {
|
|
||||||
account_id: String,
|
|
||||||
},
|
|
||||||
Worker {
|
|
||||||
runtime_id: String,
|
|
||||||
worker_id: String,
|
|
||||||
},
|
|
||||||
WorkspaceAgent {
|
|
||||||
agent_key: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct TicketActionEligibility {
|
|
||||||
pub can_assign_orchestrator: bool,
|
|
||||||
pub can_unassign_orchestrator: bool,
|
|
||||||
pub can_queue: bool,
|
|
||||||
pub can_start_manual_coder: bool,
|
|
||||||
pub queue_tickets: Vec<String>,
|
|
||||||
pub blockers: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct TicketMergeRequestSummary {
|
|
||||||
pub merge_request_id: String,
|
|
||||||
pub repository_key: String,
|
|
||||||
pub state: String,
|
|
||||||
pub review_status: String,
|
|
||||||
pub selector_from: Option<String>,
|
|
||||||
pub selector_to: String,
|
|
||||||
pub updated_at: String,
|
|
||||||
pub current_subject_ref: Option<String>,
|
|
||||||
pub review_subject_ref: Option<String>,
|
|
||||||
pub review_requested_at: Option<String>,
|
|
||||||
pub review_submitted_at: Option<String>,
|
|
||||||
pub review_excerpt: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct MergeRequestRefDiagnostic {
|
|
||||||
pub code: String,
|
|
||||||
pub message: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct MergeRequestListItem {
|
|
||||||
pub summary: TicketMergeRequestSummary,
|
|
||||||
pub ticket_ids: Vec<String>,
|
|
||||||
pub thread_event_count: usize,
|
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
||||||
pub ref_diagnostics: Vec<MergeRequestRefDiagnostic>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct MergeRequestListResponse {
|
|
||||||
pub items: Vec<MergeRequestListItem>,
|
|
||||||
pub next_cursor: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
|
|
||||||
pub struct TicketEvidenceSummary {
|
|
||||||
pub has_merge_request: bool,
|
|
||||||
pub has_current_subject_ref: bool,
|
|
||||||
pub has_review_request: bool,
|
|
||||||
pub has_commit: bool,
|
|
||||||
pub review_status: Option<String>,
|
|
||||||
pub approved_current_subject: bool,
|
|
||||||
pub review_after_rescope: bool,
|
|
||||||
pub unresolved_request_changes: bool,
|
|
||||||
pub complete_for_integration: bool,
|
|
||||||
pub missing: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
|
|
||||||
pub struct TicketQueryRequest {
|
|
||||||
pub query: Option<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub states: Vec<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub event_kinds: Vec<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub evidence: Vec<String>,
|
|
||||||
pub review_status: Option<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub attention: Vec<String>,
|
|
||||||
pub related_ticket_id: Option<String>,
|
|
||||||
pub relation_kind: Option<String>,
|
|
||||||
pub linked_objective_id: Option<String>,
|
|
||||||
pub updated_after: Option<String>,
|
|
||||||
pub updated_before: Option<String>,
|
|
||||||
pub sort: Option<String>,
|
|
||||||
pub limit: Option<usize>,
|
|
||||||
pub cursor: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct TicketQueryItem {
|
|
||||||
pub id: String,
|
|
||||||
pub resource_key: String,
|
|
||||||
pub title: String,
|
|
||||||
pub state: String,
|
|
||||||
pub readiness: Option<String>,
|
|
||||||
pub priority: String,
|
|
||||||
pub created_at: Option<String>,
|
|
||||||
pub updated_at: Option<String>,
|
|
||||||
pub item_revision: String,
|
|
||||||
pub workspace_action_priority: String,
|
|
||||||
pub matched_fields: Vec<String>,
|
|
||||||
pub snippet: Option<String>,
|
|
||||||
pub matching_event: Option<TicketEvidenceEvent>,
|
|
||||||
pub linked_objective_ids: Vec<String>,
|
|
||||||
pub linked_objective_keys: Vec<String>,
|
|
||||||
pub relation_count: usize,
|
|
||||||
pub blocker_count: usize,
|
|
||||||
pub unresolved_blocker_count: usize,
|
|
||||||
pub unresolved_review_count: usize,
|
|
||||||
pub evidence: TicketEvidenceSummary,
|
|
||||||
pub merge_request: Option<TicketMergeRequestSummary>,
|
|
||||||
pub current_coder: Option<TicketAssignmentSummary>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct TicketQueryResponse {
|
|
||||||
pub items: Vec<TicketQueryItem>,
|
|
||||||
pub page: QueryPage,
|
|
||||||
pub record_authority: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
|
|
||||||
pub struct TicketShowRequest {
|
|
||||||
pub event_limit: Option<usize>,
|
|
||||||
pub event_cursor: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
|
|
||||||
pub struct ObjectiveQueryRequest {
|
|
||||||
pub query: Option<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub states: Vec<String>,
|
|
||||||
pub linked_ticket_id: Option<String>,
|
|
||||||
pub updated_after: Option<String>,
|
|
||||||
pub updated_before: Option<String>,
|
|
||||||
pub sort: Option<String>,
|
|
||||||
pub limit: Option<usize>,
|
|
||||||
pub cursor: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct ObjectiveQueryItem {
|
|
||||||
pub id: String,
|
|
||||||
pub resource_key: String,
|
|
||||||
pub title: String,
|
|
||||||
pub state: String,
|
|
||||||
pub created_at: Option<String>,
|
|
||||||
pub updated_at: Option<String>,
|
|
||||||
pub matched_fields: Vec<String>,
|
|
||||||
pub snippet: Option<String>,
|
|
||||||
pub linked_ticket_count: usize,
|
|
||||||
pub linked_tickets: Vec<String>,
|
|
||||||
pub linked_ticket_keys: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct ObjectiveQueryResponse {
|
|
||||||
pub items: Vec<ObjectiveQueryItem>,
|
|
||||||
pub page: QueryPage,
|
|
||||||
pub record_authority: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
|
|
||||||
pub struct ObjectiveShowRequest {
|
|
||||||
pub event_limit: Option<usize>,
|
|
||||||
pub event_cursor: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn summarize_body(body: &str) -> String {
|
pub(crate) fn summarize_body(body: &str) -> String {
|
||||||
let summary = body
|
let summary = body
|
||||||
.lines()
|
.lines()
|
||||||
|
|||||||
+1653
-1226
File diff suppressed because it is too large
Load Diff
+240
-71
@@ -7,7 +7,9 @@ use std::time::{Duration, Instant};
|
|||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use client::BackendApiClient;
|
use client::BackendApiClient;
|
||||||
use futures::{SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt};
|
||||||
use server_api::{ExternalWorkdirGrantCreateRequest, ExternalWorkdirGrantResponse};
|
use server_api::{
|
||||||
|
ExternalWorkdirGrantCreateRequest, ExternalWorkdirGrantResponse, ServerApiClient,
|
||||||
|
};
|
||||||
use tokio_tungstenite::connect_async_with_config;
|
use tokio_tungstenite::connect_async_with_config;
|
||||||
use tokio_tungstenite::tungstenite::{Message, protocol::WebSocketConfig};
|
use tokio_tungstenite::tungstenite::{Message, protocol::WebSocketConfig};
|
||||||
use workdir::external::{
|
use workdir::external::{
|
||||||
@@ -95,40 +97,57 @@ fn pin_selected_root_from(
|
|||||||
ExternalWorkdirRoot::pin(absolute).map_err(|error| error.to_string())
|
ExternalWorkdirRoot::pin(absolute).map_err(|error| error.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn authenticated_server_api_client(
|
||||||
|
backend_client: &BackendApiClient,
|
||||||
|
backend_url: &str,
|
||||||
|
) -> Result<ServerApiClient, String> {
|
||||||
|
// BackendApiClient remains the stored-credential authority used by the
|
||||||
|
// manual provider WebSocket. Reuse that transport's Authorization header
|
||||||
|
// for the generated JSON client instead of loading or interpreting the
|
||||||
|
// credential a second time here.
|
||||||
|
let authenticated_request = backend_client
|
||||||
|
.websocket_request("/")
|
||||||
|
.map_err(|error| error.to_string())?;
|
||||||
|
let authorization = authenticated_request
|
||||||
|
.headers()
|
||||||
|
.get(reqwest::header::AUTHORIZATION)
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| "stored Backend credential did not provide Authorization".to_string())?;
|
||||||
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
|
headers.insert(reqwest::header::AUTHORIZATION, authorization);
|
||||||
|
let http_client = reqwest::Client::builder()
|
||||||
|
.default_headers(headers)
|
||||||
|
.timeout(Duration::from_secs(10))
|
||||||
|
.build()
|
||||||
|
.map_err(|error| format!("failed to configure Backend client: {error}"))?;
|
||||||
|
let base_url = backend_url
|
||||||
|
.try_into()
|
||||||
|
.map_err(|error| format!("invalid Backend URL: {error}"))?;
|
||||||
|
Ok(ServerApiClient::with_client(base_url, http_client))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn run(options: WorkdirShareOptions) -> Result<(), String> {
|
pub(crate) async fn run(options: WorkdirShareOptions) -> Result<(), String> {
|
||||||
// Pin before creating remote authority: the Backend grant can never outlive
|
// Pin before creating remote authority: the Backend grant can never outlive
|
||||||
// a failed local approval/open race, and subsequent path replacement cannot
|
// a failed local approval/open race, and subsequent path replacement cannot
|
||||||
// redirect the provider.
|
// redirect the provider.
|
||||||
let pinned_root = pin_selected_root(&options.path)?;
|
let pinned_root = pin_selected_root(&options.path)?;
|
||||||
|
|
||||||
let client = BackendApiClient::from_stored_token(&options.backend_url)
|
let backend_client = BackendApiClient::from_stored_token(&options.backend_url)
|
||||||
.map_err(|error| error.to_string())?;
|
.map_err(|error| error.to_string())?;
|
||||||
|
let server_api_client = authenticated_server_api_client(&backend_client, &options.backend_url)?;
|
||||||
let provider_instance_id = format!("cli-{}", uuid::Uuid::now_v7());
|
let provider_instance_id = format!("cli-{}", uuid::Uuid::now_v7());
|
||||||
let create_path = format!(
|
let grant = server_api_client
|
||||||
"/api/w/{}/external-workdir-grants",
|
.external_workdir_grant_create(
|
||||||
encode_path_segment(&options.workspace_id)
|
options.workspace_id.clone(),
|
||||||
);
|
ExternalWorkdirGrantCreateRequest {
|
||||||
let response = client
|
provider_instance_id: provider_instance_id.clone(),
|
||||||
.request(reqwest::Method::POST, &create_path)
|
display_name: options.display_name.clone(),
|
||||||
.map_err(|error| error.to_string())?
|
ttl_seconds: options.ttl.as_secs(),
|
||||||
.timeout(Duration::from_secs(10))
|
read_only: true,
|
||||||
.json(&ExternalWorkdirGrantCreateRequest {
|
},
|
||||||
provider_instance_id: provider_instance_id.clone(),
|
)
|
||||||
display_name: options.display_name.clone(),
|
|
||||||
ttl_seconds: options.ttl.as_secs(),
|
|
||||||
read_only: true,
|
|
||||||
})
|
|
||||||
.send()
|
|
||||||
.await
|
.await
|
||||||
.map_err(|error| format!("External Workdir grant request failed: {error}"))?;
|
.map_err(|error| format!("External Workdir grant request failed: {error}"))?;
|
||||||
let response = client
|
|
||||||
.require_success(response)
|
|
||||||
.await
|
|
||||||
.map_err(|error| error.to_string())?;
|
|
||||||
let grant = response
|
|
||||||
.json::<ExternalWorkdirGrantResponse>()
|
|
||||||
.await
|
|
||||||
.map_err(|error| format!("Backend returned an invalid External Workdir grant: {error}"))?;
|
|
||||||
|
|
||||||
let limits = BoundedReadLimits::EXTERNAL_DEFAULT;
|
let limits = BoundedReadLimits::EXTERNAL_DEFAULT;
|
||||||
let session = LocalWorkdirSession::external_read_only_pinned(
|
let session = LocalWorkdirSession::external_read_only_pinned(
|
||||||
@@ -150,7 +169,8 @@ pub(crate) async fn run(options: WorkdirShareOptions) -> Result<(), String> {
|
|||||||
let mut generation = grant.generation;
|
let mut generation = grant.generation;
|
||||||
loop {
|
loop {
|
||||||
let outcome = serve_provider_connection(
|
let outcome = serve_provider_connection(
|
||||||
&client,
|
&backend_client,
|
||||||
|
&server_api_client,
|
||||||
&options,
|
&options,
|
||||||
&grant,
|
&grant,
|
||||||
&provider_instance_id,
|
&provider_instance_id,
|
||||||
@@ -166,11 +186,12 @@ pub(crate) async fn run(options: WorkdirShareOptions) -> Result<(), String> {
|
|||||||
"External Workdir grant expired after provider connection failure: {connection_error}"
|
"External Workdir grant expired after provider connection failure: {connection_error}"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let refresh = fetch_grant(&client, &options.workspace_id, &grant.grant_id);
|
let refresh =
|
||||||
|
fetch_grant(&server_api_client, &options.workspace_id, &grant.grant_id);
|
||||||
let refreshed = tokio::select! {
|
let refreshed = tokio::select! {
|
||||||
signal = tokio::signal::ctrl_c() => {
|
signal = tokio::signal::ctrl_c() => {
|
||||||
signal.map_err(|error| format!("failed to wait for Ctrl-C: {error}"))?;
|
signal.map_err(|error| format!("failed to wait for Ctrl-C: {error}"))?;
|
||||||
revoke(&client, &options.workspace_id, &grant.grant_id).await?;
|
revoke(&server_api_client, &options.workspace_id, &grant.grant_id).await?;
|
||||||
println!("External Workdir grant revoked.");
|
println!("External Workdir grant revoked.");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -182,8 +203,12 @@ pub(crate) async fn run(options: WorkdirShareOptions) -> Result<(), String> {
|
|||||||
eprintln!(
|
eprintln!(
|
||||||
"Connection refresh failed; retrying: {connection_error}; {refresh_error}"
|
"Connection refresh failed; retrying: {connection_error}; {refresh_error}"
|
||||||
);
|
);
|
||||||
if wait_to_reconnect(&client, &options.workspace_id, &grant.grant_id)
|
if wait_to_reconnect(
|
||||||
.await?
|
&server_api_client,
|
||||||
|
&options.workspace_id,
|
||||||
|
&grant.grant_id,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
{
|
{
|
||||||
println!("External Workdir grant revoked.");
|
println!("External Workdir grant revoked.");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -213,7 +238,9 @@ pub(crate) async fn run(options: WorkdirShareOptions) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
println!("Connection: reconnecting");
|
println!("Connection: reconnecting");
|
||||||
if wait_to_reconnect(&client, &options.workspace_id, &grant.grant_id).await? {
|
if wait_to_reconnect(&server_api_client, &options.workspace_id, &grant.grant_id)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
println!("External Workdir grant revoked.");
|
println!("External Workdir grant revoked.");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -237,7 +264,9 @@ pub(crate) async fn run(options: WorkdirShareOptions) -> Result<(), String> {
|
|||||||
.checked_add(1)
|
.checked_add(1)
|
||||||
.ok_or_else(|| "External Workdir provider generation overflow".to_string())?;
|
.ok_or_else(|| "External Workdir provider generation overflow".to_string())?;
|
||||||
println!("Connection: reconnecting");
|
println!("Connection: reconnecting");
|
||||||
if wait_to_reconnect(&client, &options.workspace_id, &grant.grant_id).await? {
|
if wait_to_reconnect(&server_api_client, &options.workspace_id, &grant.grant_id)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
println!("External Workdir grant revoked.");
|
println!("External Workdir grant revoked.");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -247,7 +276,8 @@ pub(crate) async fn run(options: WorkdirShareOptions) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn serve_provider_connection(
|
async fn serve_provider_connection(
|
||||||
client: &BackendApiClient,
|
backend_client: &BackendApiClient,
|
||||||
|
server_api_client: &ServerApiClient,
|
||||||
options: &WorkdirShareOptions,
|
options: &WorkdirShareOptions,
|
||||||
grant: &ExternalWorkdirGrantResponse,
|
grant: &ExternalWorkdirGrantResponse,
|
||||||
provider_instance_id: &str,
|
provider_instance_id: &str,
|
||||||
@@ -259,7 +289,7 @@ async fn serve_provider_connection(
|
|||||||
encode_path_segment(&options.workspace_id),
|
encode_path_segment(&options.workspace_id),
|
||||||
encode_path_segment(&grant.grant_id)
|
encode_path_segment(&grant.grant_id)
|
||||||
);
|
);
|
||||||
let request = client
|
let request = backend_client
|
||||||
.websocket_request(&provider_path)
|
.websocket_request(&provider_path)
|
||||||
.map_err(|error| error.to_string())?;
|
.map_err(|error| error.to_string())?;
|
||||||
let websocket_config = WebSocketConfig::default()
|
let websocket_config = WebSocketConfig::default()
|
||||||
@@ -269,7 +299,7 @@ async fn serve_provider_connection(
|
|||||||
let (mut socket, _) = tokio::select! {
|
let (mut socket, _) = tokio::select! {
|
||||||
signal = tokio::signal::ctrl_c() => {
|
signal = tokio::signal::ctrl_c() => {
|
||||||
signal.map_err(|error| format!("failed to wait for Ctrl-C: {error}"))?;
|
signal.map_err(|error| format!("failed to wait for Ctrl-C: {error}"))?;
|
||||||
revoke(client, &options.workspace_id, &grant.grant_id).await?;
|
revoke(server_api_client, &options.workspace_id, &grant.grant_id).await?;
|
||||||
return Ok(ProviderEnd::Interrupted);
|
return Ok(ProviderEnd::Interrupted);
|
||||||
}
|
}
|
||||||
connection = connection => connection
|
connection = connection => connection
|
||||||
@@ -292,7 +322,7 @@ async fn serve_provider_connection(
|
|||||||
let registered = tokio::select! {
|
let registered = tokio::select! {
|
||||||
signal = tokio::signal::ctrl_c() => {
|
signal = tokio::signal::ctrl_c() => {
|
||||||
signal.map_err(|error| format!("failed to wait for Ctrl-C: {error}"))?;
|
signal.map_err(|error| format!("failed to wait for Ctrl-C: {error}"))?;
|
||||||
revoke(client, &options.workspace_id, &grant.grant_id).await?;
|
revoke(server_api_client, &options.workspace_id, &grant.grant_id).await?;
|
||||||
return Ok(ProviderEnd::Interrupted);
|
return Ok(ProviderEnd::Interrupted);
|
||||||
}
|
}
|
||||||
registered = tokio::time::timeout(Duration::from_secs(10), socket.next()) => {
|
registered = tokio::time::timeout(Duration::from_secs(10), socket.next()) => {
|
||||||
@@ -324,7 +354,7 @@ async fn serve_provider_connection(
|
|||||||
signal = tokio::signal::ctrl_c() => {
|
signal = tokio::signal::ctrl_c() => {
|
||||||
signal.map_err(|error| format!("failed to wait for Ctrl-C: {error}"))?;
|
signal.map_err(|error| format!("failed to wait for Ctrl-C: {error}"))?;
|
||||||
stop_operations(&mut operations, &mut completions).await?;
|
stop_operations(&mut operations, &mut completions).await?;
|
||||||
revoke(client, &options.workspace_id, &grant.grant_id).await?;
|
revoke(server_api_client, &options.workspace_id, &grant.grant_id).await?;
|
||||||
return Ok(ProviderEnd::Interrupted);
|
return Ok(ProviderEnd::Interrupted);
|
||||||
}
|
}
|
||||||
completion = completions.recv() => {
|
completion = completions.recv() => {
|
||||||
@@ -558,7 +588,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn wait_to_reconnect(
|
async fn wait_to_reconnect(
|
||||||
client: &BackendApiClient,
|
client: &ServerApiClient,
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
grant_id: &str,
|
grant_id: &str,
|
||||||
) -> Result<bool, String> {
|
) -> Result<bool, String> {
|
||||||
@@ -573,51 +603,27 @@ async fn wait_to_reconnect(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_grant(
|
async fn fetch_grant(
|
||||||
client: &BackendApiClient,
|
client: &ServerApiClient,
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
grant_id: &str,
|
grant_id: &str,
|
||||||
) -> Result<ExternalWorkdirGrantResponse, String> {
|
) -> Result<ExternalWorkdirGrantResponse, String> {
|
||||||
let path = format!(
|
|
||||||
"/api/w/{}/external-workdir-grants/{}",
|
|
||||||
encode_path_segment(workspace_id),
|
|
||||||
encode_path_segment(grant_id)
|
|
||||||
);
|
|
||||||
let response = client
|
|
||||||
.request(reqwest::Method::GET, &path)
|
|
||||||
.map_err(|error| error.to_string())?
|
|
||||||
.timeout(Duration::from_secs(10))
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.map_err(|error| format!("External Workdir grant refresh failed: {error}"))?;
|
|
||||||
client
|
client
|
||||||
.require_success(response)
|
.external_workdir_grant_get(workspace_id.to_string(), grant_id.to_string())
|
||||||
.await
|
.await
|
||||||
.map_err(|error| error.to_string())?
|
.map_err(|error| format!("External Workdir grant refresh failed: {error}"))
|
||||||
.json::<ExternalWorkdirGrantResponse>()
|
|
||||||
.await
|
|
||||||
.map_err(|error| format!("Backend returned an invalid External Workdir grant: {error}"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn revoke(
|
async fn revoke(
|
||||||
client: &BackendApiClient,
|
client: &ServerApiClient,
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
grant_id: &str,
|
grant_id: &str,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let path = format!(
|
client
|
||||||
"/api/w/{}/external-workdir-grants/{}",
|
.external_workdir_grant_revoke(workspace_id.to_string(), grant_id.to_string())
|
||||||
encode_path_segment(workspace_id),
|
|
||||||
encode_path_segment(grant_id)
|
|
||||||
);
|
|
||||||
let response = client
|
|
||||||
.request(reqwest::Method::DELETE, &path)
|
|
||||||
.map_err(|error| error.to_string())?
|
|
||||||
.timeout(Duration::from_secs(10))
|
|
||||||
.send()
|
|
||||||
.await
|
.await
|
||||||
.map_err(|error| format!("External Workdir revoke request failed: {error}"))?;
|
.map_err(|error| {
|
||||||
client.require_success(response).await.map_err(|error| {
|
format!("External Workdir revoke was not confirmed by the Backend: {error}")
|
||||||
format!("External Workdir revoke was not confirmed by the Backend: {error}")
|
})?;
|
||||||
})?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,6 +642,169 @@ fn encode_path_segment(value: &str) -> String {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::net::{TcpListener, TcpStream};
|
||||||
|
use std::sync::mpsc;
|
||||||
|
|
||||||
|
fn test_server_api_client(base_url: &str) -> ServerApiClient {
|
||||||
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
|
headers.insert(
|
||||||
|
reqwest::header::AUTHORIZATION,
|
||||||
|
reqwest::header::HeaderValue::from_static("Bearer test-token"),
|
||||||
|
);
|
||||||
|
let http_client = reqwest::Client::builder()
|
||||||
|
.default_headers(headers)
|
||||||
|
.timeout(Duration::from_secs(10))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
ServerApiClient::with_client(base_url.try_into().unwrap(), http_client)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_http_request(stream: &mut TcpStream) -> String {
|
||||||
|
stream
|
||||||
|
.set_read_timeout(Some(Duration::from_secs(5)))
|
||||||
|
.unwrap();
|
||||||
|
let mut request = Vec::new();
|
||||||
|
let mut buffer = [0_u8; 4096];
|
||||||
|
let mut expected_length = None;
|
||||||
|
loop {
|
||||||
|
let read = stream.read(&mut buffer).unwrap();
|
||||||
|
assert!(read > 0, "HTTP client closed before completing request");
|
||||||
|
request.extend_from_slice(&buffer[..read]);
|
||||||
|
if expected_length.is_none()
|
||||||
|
&& let Some(header_end) =
|
||||||
|
request.windows(4).position(|window| window == b"\r\n\r\n")
|
||||||
|
{
|
||||||
|
let headers = String::from_utf8_lossy(&request[..header_end]);
|
||||||
|
let content_length = headers.lines().find_map(|line| {
|
||||||
|
let (name, value) = line.split_once(':')?;
|
||||||
|
name.eq_ignore_ascii_case("content-length")
|
||||||
|
.then(|| value.trim().parse::<usize>().unwrap())
|
||||||
|
});
|
||||||
|
expected_length = Some(header_end + 4 + content_length.unwrap_or(0));
|
||||||
|
}
|
||||||
|
if expected_length.is_some_and(|length| request.len() >= length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String::from_utf8(request).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_json_server(
|
||||||
|
response_body: String,
|
||||||
|
response_count: usize,
|
||||||
|
) -> (String, mpsc::Receiver<String>, std::thread::JoinHandle<()>) {
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
|
let address = listener.local_addr().unwrap();
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
let thread = std::thread::spawn(move || {
|
||||||
|
for _ in 0..response_count {
|
||||||
|
let (mut stream, _) = listener.accept().unwrap();
|
||||||
|
let request = read_http_request(&mut stream);
|
||||||
|
let status = if request.starts_with("POST ") {
|
||||||
|
"201 Created"
|
||||||
|
} else {
|
||||||
|
"200 OK"
|
||||||
|
};
|
||||||
|
sender.send(request).unwrap();
|
||||||
|
write!(
|
||||||
|
stream,
|
||||||
|
"HTTP/1.1 {status}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
|
||||||
|
response_body.len(),
|
||||||
|
response_body
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
(format!("http://{address}"), receiver, thread)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn grant_response_json() -> String {
|
||||||
|
serde_json::json!({
|
||||||
|
"grant_id": "grant/a b",
|
||||||
|
"workspace_id": "workspace/a b",
|
||||||
|
"working_directory_id": "workdir-a",
|
||||||
|
"provider_instance_id": "provider-a",
|
||||||
|
"display_name": "Shared files",
|
||||||
|
"permissions": "read_only",
|
||||||
|
"expires_at": "2026-09-23T03:00:00Z",
|
||||||
|
"generation": 7,
|
||||||
|
"status": "pending"
|
||||||
|
})
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn generated_grant_operations_preserve_auth_and_encode_contract_paths() {
|
||||||
|
let (base_url, requests, server) = spawn_json_server(grant_response_json(), 3);
|
||||||
|
let client = test_server_api_client(&base_url);
|
||||||
|
|
||||||
|
let created = client
|
||||||
|
.external_workdir_grant_create(
|
||||||
|
"workspace/a b".to_string(),
|
||||||
|
ExternalWorkdirGrantCreateRequest {
|
||||||
|
provider_instance_id: "provider-a".to_string(),
|
||||||
|
display_name: "Shared files".to_string(),
|
||||||
|
ttl_seconds: 600,
|
||||||
|
read_only: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let fetched = fetch_grant(&client, "workspace/a b", "grant/a b")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
revoke(&client, "workspace/a b", "grant/a b").await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(created.grant_id, "grant/a b");
|
||||||
|
assert_eq!(fetched.generation, 7);
|
||||||
|
let requests = (0..3)
|
||||||
|
.map(|_| requests.recv_timeout(Duration::from_secs(5)).unwrap())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
server.join().unwrap();
|
||||||
|
assert!(
|
||||||
|
requests[0]
|
||||||
|
.starts_with("POST /api/w/workspace%2Fa%20b/external-workdir-grants HTTP/1.1\r\n")
|
||||||
|
);
|
||||||
|
assert!(requests[1].starts_with(
|
||||||
|
"GET /api/w/workspace%2Fa%20b/external-workdir-grants/grant%2Fa%20b HTTP/1.1\r\n"
|
||||||
|
));
|
||||||
|
assert!(requests[2].starts_with(
|
||||||
|
"DELETE /api/w/workspace%2Fa%20b/external-workdir-grants/grant%2Fa%20b HTTP/1.1\r\n"
|
||||||
|
));
|
||||||
|
for request in &requests {
|
||||||
|
assert!(
|
||||||
|
request
|
||||||
|
.to_ascii_lowercase()
|
||||||
|
.contains("\r\nauthorization: bearer test-token\r\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert!(requests[0].contains("\"provider_instance_id\":\"provider-a\""));
|
||||||
|
assert!(requests[0].contains("\"ttl_seconds\":600"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn generated_grant_response_is_bounded() {
|
||||||
|
let oversized_body = format!("{{\"padding\":\"{}\"}}", "x".repeat(1_048_576));
|
||||||
|
let (base_url, requests, server) = spawn_json_server(oversized_body, 1);
|
||||||
|
let client = test_server_api_client(&base_url);
|
||||||
|
|
||||||
|
let error = fetch_grant(&client, "workspace-a", "grant-a")
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
let request = requests.recv_timeout(Duration::from_secs(5)).unwrap();
|
||||||
|
server.join().unwrap();
|
||||||
|
assert!(
|
||||||
|
request
|
||||||
|
.starts_with("GET /api/w/workspace-a/external-workdir-grants/grant-a HTTP/1.1\r\n")
|
||||||
|
);
|
||||||
|
assert!(error.contains("External Workdir grant refresh failed"));
|
||||||
|
assert!(
|
||||||
|
error.contains("response body exceeded 1048576 bytes"),
|
||||||
|
"{error}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn relative_share_path_is_resolved_and_pinned_before_backend_access() {
|
fn relative_share_path_is_resolved_and_pinned_before_backend_access() {
|
||||||
|
|||||||
+335
-4173
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
// @generated by yoi-companion-openapi-typescript@1. Do not edit.
|
// @generated by yoi-companion-openapi-typescript@1. Do not edit.
|
||||||
// Input: openapi/server-api.json
|
// Input: openapi/server-api.json
|
||||||
// Input contract: 0.1.0 (sha256:550383b600fe2a2e55630849c7de7a6a24463ad41d0c18dc5d254a3d14328f07)
|
// Input contract: 0.1.0 (sha256:6b26856793bfd6dd3d34461b3a7422adfdf0fab74c0ebab5ef6ab7fbdf034dfe)
|
||||||
// Options: companion-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
// Options: companion-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
||||||
// Output: web/workspace/src/lib/generated/companion-api.ts
|
// Output: web/workspace/src/lib/generated/companion-api.ts
|
||||||
// Regenerate/check: cargo run -q -p server-api --example generate_companion_api_types -- --check
|
// Regenerate/check: cargo run -q -p server-api --example generate_companion_api_types -- --check
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// @generated by yoi-repository-openapi-typescript@1. Do not edit.
|
// @generated by yoi-repository-openapi-typescript@1. Do not edit.
|
||||||
// Input: openapi/server-api.json
|
// Input: openapi/server-api.json
|
||||||
// Input contract: 0.1.0 (sha256:550383b600fe2a2e55630849c7de7a6a24463ad41d0c18dc5d254a3d14328f07)
|
// Input contract: 0.1.0 (sha256:6b26856793bfd6dd3d34461b3a7422adfdf0fab74c0ebab5ef6ab7fbdf034dfe)
|
||||||
// Options: repository-operation-schema-closure,openapi-3.1,format:uint64=safe-number,strict-safe-integers,sorted-output
|
// Options: repository-operation-schema-closure,openapi-3.1,format:uint64=safe-number,strict-safe-integers,sorted-output
|
||||||
// Output: web/workspace/src/lib/generated/repository-api.ts
|
// Output: web/workspace/src/lib/generated/repository-api.ts
|
||||||
// Regenerate/check: cargo run -q -p server-api --example generate_repository_openapi_types -- --check
|
// Regenerate/check: cargo run -q -p server-api --example generate_repository_openapi_types -- --check
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// @generated by yoi-runtime-openapi-typescript@1. Do not edit.
|
// @generated by yoi-runtime-openapi-typescript@1. Do not edit.
|
||||||
// Input: openapi/server-api.json
|
// Input: openapi/server-api.json
|
||||||
// Input contract: 0.1.0 (sha256:550383b600fe2a2e55630849c7de7a6a24463ad41d0c18dc5d254a3d14328f07)
|
// Input contract: 0.1.0 (sha256:6b26856793bfd6dd3d34461b3a7422adfdf0fab74c0ebab5ef6ab7fbdf034dfe)
|
||||||
// Options: runtime-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
// Options: runtime-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
||||||
// Output: web/workspace/src/lib/generated/runtime-api.ts
|
// Output: web/workspace/src/lib/generated/runtime-api.ts
|
||||||
// Regenerate/check: cargo run -q -p server-api --example generate_runtime_api_types -- --check
|
// Regenerate/check: cargo run -q -p server-api --example generate_runtime_api_types -- --check
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// @generated by yoi-ticket-browser-openapi-typescript@1. Do not edit.
|
// @generated by yoi-ticket-browser-openapi-typescript@1. Do not edit.
|
||||||
// Input: openapi/server-api.json
|
// Input: openapi/server-api.json
|
||||||
// Input contract: 0.1.0 (sha256:550383b600fe2a2e55630849c7de7a6a24463ad41d0c18dc5d254a3d14328f07)
|
// Input contract: 0.1.0 (sha256:6b26856793bfd6dd3d34461b3a7422adfdf0fab74c0ebab5ef6ab7fbdf034dfe)
|
||||||
// Options: ticket-objective-merge-request-browser-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
// Options: ticket-objective-merge-request-browser-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
||||||
// Output: web/workspace/src/lib/generated/ticket-api.ts
|
// Output: web/workspace/src/lib/generated/ticket-api.ts
|
||||||
// Regenerate/check: cargo run -q -p server-api --example generate_ticket_api_types -- --check
|
// Regenerate/check: cargo run -q -p server-api --example generate_ticket_api_types -- --check
|
||||||
|
|||||||
@@ -1,84 +1,20 @@
|
|||||||
// @generated by yoi-workdir-openapi-typescript@1. Do not edit.
|
// @generated by yoi-workdir-openapi-typescript@1. Do not edit.
|
||||||
// Input: openapi/server-api.json
|
// Input: openapi/server-api.json
|
||||||
// Input contract: 0.1.0 (sha256:550383b600fe2a2e55630849c7de7a6a24463ad41d0c18dc5d254a3d14328f07)
|
// Input contract: 0.1.0 (sha256:6b26856793bfd6dd3d34461b3a7422adfdf0fab74c0ebab5ef6ab7fbdf034dfe)
|
||||||
// Options: workdir-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
// Options: workdir-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
||||||
// Output: web/workspace/src/lib/generated/workdir-api.ts
|
// Output: web/workspace/src/lib/generated/workdir-api.ts
|
||||||
// Regenerate/check: cargo run -q -p server-api --example generate_workdir_api_types -- --check
|
// Regenerate/check: cargo run -q -p server-api --example generate_workdir_api_types -- --check
|
||||||
|
|
||||||
export type CommandOutput = { content: string; exit_code?: number | null; next_cursor?: number | null; output_path?: string | null; status: CommandStatus; timed_out: boolean; truncated: boolean; };
|
|
||||||
|
|
||||||
export type CommandOutputRequest = { cursor: number; handle: string; limit: number; wait: boolean; };
|
|
||||||
|
|
||||||
export type CommandRequest = { command: string; cwd?: FsPath | null; output_limit: number; spill_dir?: string | null; timeout_secs: number; tool_call_id?: string | null; };
|
|
||||||
|
|
||||||
export type CommandStatus = "running" | "completed" | "failed" | "timed_out" | "cancelled";
|
|
||||||
|
|
||||||
export type CurrentWorkerWorkdirAttachRequest = { alias: string; working_directory_id: string; };
|
|
||||||
|
|
||||||
export type CurrentWorkerWorkdirAttachmentResponse = { alias: string; attached: boolean; capabilities: WorkdirSessionCapabilities; working_directory_id: string; workspace_id: string; };
|
|
||||||
|
|
||||||
export type CurrentWorkerWorkdirOperationRequest = { operation: WorkdirSessionOperation; target_workdir: string; };
|
|
||||||
|
|
||||||
export type Diagnostic = { code: string; message: string; severity: DiagnosticSeverity; };
|
export type Diagnostic = { code: string; message: string; severity: DiagnosticSeverity; };
|
||||||
|
|
||||||
export type DiagnosticSeverity = "info" | "warning" | "error";
|
export type DiagnosticSeverity = "info" | "warning" | "error";
|
||||||
|
|
||||||
export type EditRequest = { expected_hash: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number]; new_string: string; old_string: string; path: FsPath; replace_all: boolean; };
|
|
||||||
|
|
||||||
export type EditResult = { bytes_written: number; content_hash: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number]; replacements: number; };
|
|
||||||
|
|
||||||
export type EntryKind = "file" | "directory" | "symlink" | "other";
|
|
||||||
|
|
||||||
export type ExternalWorkdirGrantCreateRequest = { display_name: string; provider_instance_id: string; read_only: boolean; ttl_seconds: number; };
|
export type ExternalWorkdirGrantCreateRequest = { display_name: string; provider_instance_id: string; read_only: boolean; ttl_seconds: number; };
|
||||||
|
|
||||||
export type ExternalWorkdirGrantResponse = { display_name: string; expires_at: string; generation: number; grant_id: string; permissions: string; provider_instance_id: string; status: string; working_directory_id: string; workspace_id: string; };
|
export type ExternalWorkdirGrantResponse = { display_name: string; expires_at: string; generation: number; grant_id: string; permissions: string; provider_instance_id: string; status: string; working_directory_id: string; workspace_id: string; };
|
||||||
|
|
||||||
export type FsPath = string;
|
|
||||||
|
|
||||||
export type GlobRequest = { limit: number; path: FsPath; pattern: string; };
|
|
||||||
|
|
||||||
export type GlobResult = { paths: Array<FsPath>; truncated: boolean; };
|
|
||||||
|
|
||||||
export type GrepOutputMode = "content" | "files_with_matches" | "count";
|
|
||||||
|
|
||||||
export type GrepRequest = { after_context: number; before_context: number; case_insensitive: boolean; file_type?: string | null; glob?: string | null; limit: number; multiline: boolean; offset: number; output_mode: GrepOutputMode; path: FsPath; pattern: string; };
|
|
||||||
|
|
||||||
export type GrepResult = { match_count: number; matched_files: number; output: string; truncated: boolean; };
|
|
||||||
|
|
||||||
export type ListEntry = { kind: EntryKind; path: FsPath; size: number; };
|
|
||||||
|
|
||||||
export type ListRequest = { limit: number; path: FsPath; };
|
|
||||||
|
|
||||||
export type ListResult = { entries: Array<ListEntry>; total_bytes: number; total_entries: number; truncated: boolean; };
|
|
||||||
|
|
||||||
export type ReadRequest = { limit: number; max_bytes: number; offset: number; path: FsPath; };
|
|
||||||
|
|
||||||
export type ReadResult = { bytes: Array<number>; content_hash: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number]; path: FsPath; start_line: number; total_lines: number; truncated: boolean; };
|
|
||||||
|
|
||||||
export type RepositoryApiError = { diagnostics?: Array<Diagnostic>; error: string; message: string; };
|
export type RepositoryApiError = { diagnostics?: Array<Diagnostic>; error: string; message: string; };
|
||||||
|
|
||||||
export type StatRequest = { path: FsPath; };
|
|
||||||
|
|
||||||
export type StatResult = { kind: EntryKind; path: FsPath; size: number; };
|
|
||||||
|
|
||||||
export type WorkdirOperationApiError = { code?: WorkdirTransportErrorCode | null; diagnostics?: Array<Diagnostic> | null; error?: string | null; message: string; };
|
|
||||||
|
|
||||||
export type WorkdirScopeAuthorizationRequest = { path: FsPath; permission: WorkdirToolScopePermission; rules: Array<WorkdirToolScopeRule>; };
|
|
||||||
|
|
||||||
export type WorkdirScopeOverlapRequest = { left: WorkdirToolScopeRule; right: WorkdirToolScopeRule; };
|
|
||||||
|
|
||||||
export type WorkdirSessionCapabilities = { bits: number; };
|
|
||||||
|
|
||||||
export type WorkdirSessionOperation = { operation: "authorize_scope"; request: WorkdirScopeAuthorizationRequest; } | { operation: "scope_rules_overlap"; request: WorkdirScopeOverlapRequest; } | { operation: "stat"; request: StatRequest; } | { operation: "read"; request: ReadRequest; } | { operation: "write"; request: WriteRequest; } | { operation: "edit"; request: EditRequest; } | { operation: "list"; request: ListRequest; } | { operation: "glob"; request: GlobRequest; } | { operation: "grep"; request: GrepRequest; } | { operation: "command_start"; request: CommandRequest; } | { operation: "command_status"; request: string; } | { operation: "command_output"; request: CommandOutputRequest; } | { operation: "command_cancel"; request: string; };
|
|
||||||
|
|
||||||
export type WorkdirSessionOperationResult = { operation: "authorize_scope"; } | { operation: "scope_rules_overlap"; result: { overlaps: boolean; }; } | { operation: "stat"; result: StatResult; } | { operation: "read"; result: ReadResult; } | { operation: "write"; result: WriteResult; } | { operation: "edit"; result: EditResult; } | { operation: "list"; result: ListResult; } | { operation: "glob"; result: GlobResult; } | { operation: "grep"; result: GrepResult; } | { operation: "command_start"; result: string; } | { operation: "command_status"; result: CommandStatus; } | { operation: "command_output"; result: CommandOutput; } | { operation: "command_cancel"; };
|
|
||||||
|
|
||||||
export type WorkdirToolScopePermission = "read" | "write";
|
|
||||||
|
|
||||||
export type WorkdirToolScopeRule = { permission: WorkdirToolScopePermission; recursive: boolean; symlink_policy?: string; target: FsPath; };
|
|
||||||
|
|
||||||
export type WorkdirTransportErrorCode = "not_found" | "conflict" | "unsupported" | "invalid_request" | "denied" | "out_of_scope" | "symlink_out_of_scope" | "broken_symlink" | "symlink_target_is_directory" | "read_only" | "is_directory" | "symlink_directory_not_traversed" | "unknown_command" | "unavailable" | "io" | "transport" | "internal";
|
|
||||||
|
|
||||||
export type WorkingDirectoryCleanupTarget = { kind: string; repository_key: string; working_directory_id: string; };
|
export type WorkingDirectoryCleanupTarget = { kind: string; repository_key: string; working_directory_id: string; };
|
||||||
|
|
||||||
export type WorkingDirectoryCreateRequest = { display_name?: string | null; operation_id?: string | null; repository_key: string; runtime_id?: string | null; selector?: string | null; };
|
export type WorkingDirectoryCreateRequest = { display_name?: string | null; operation_id?: string | null; repository_key: string; runtime_id?: string | null; selector?: string | null; };
|
||||||
@@ -104,7 +40,3 @@ export type WorkingDirectorySource = { kind: "repository"; repository_key: strin
|
|||||||
export type WorkingDirectoryStatusKind = "active" | "cleanup_pending" | "corrupted" | "not_found" | "unknown";
|
export type WorkingDirectoryStatusKind = "active" | "cleanup_pending" | "corrupted" | "not_found" | "unknown";
|
||||||
|
|
||||||
export type WorkingDirectorySummary = { cleanliness?: string | null; cleanup_target?: WorkingDirectoryCleanupTarget | null; creation_ref?: string | null; creation_selector?: string | null; creation_tree?: string | null; current_ref?: string | null; current_selector?: string | null; current_tree?: string | null; display_name?: string | null; materializer_kind: WorkingDirectoryMaterializerKind; observed_at_epoch_seconds?: number | null; occupied_by?: WorkingDirectoryOccupancy | null; source: WorkingDirectorySource; status: WorkingDirectoryStatusKind; working_directory_id: string; };
|
export type WorkingDirectorySummary = { cleanliness?: string | null; cleanup_target?: WorkingDirectoryCleanupTarget | null; creation_ref?: string | null; creation_selector?: string | null; creation_tree?: string | null; current_ref?: string | null; current_selector?: string | null; current_tree?: string | null; display_name?: string | null; materializer_kind: WorkingDirectoryMaterializerKind; observed_at_epoch_seconds?: number | null; occupied_by?: WorkingDirectoryOccupancy | null; source: WorkingDirectorySource; status: WorkingDirectoryStatusKind; working_directory_id: string; };
|
||||||
|
|
||||||
export type WriteRequest = { content: Array<number>; expected_hash?: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number] | null; path: FsPath; };
|
|
||||||
|
|
||||||
export type WriteResult = { bytes_written: number; created: boolean; };
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// @generated by yoi-worker-openapi-typescript@1. Do not edit.
|
// @generated by yoi-worker-openapi-typescript@1. Do not edit.
|
||||||
// Input: openapi/server-api.json
|
// Input: openapi/server-api.json
|
||||||
// Input contract: 0.1.0 (sha256:550383b600fe2a2e55630849c7de7a6a24463ad41d0c18dc5d254a3d14328f07)
|
// Input contract: 0.1.0 (sha256:6b26856793bfd6dd3d34461b3a7422adfdf0fab74c0ebab5ef6ab7fbdf034dfe)
|
||||||
// Options: worker-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
// Options: worker-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
||||||
// Output: web/workspace/src/lib/generated/worker-launch-api.ts
|
// Output: web/workspace/src/lib/generated/worker-launch-api.ts
|
||||||
// Regenerate/check: cargo run -q -p server-api --example generate_worker_launch_api_types -- --check
|
// Regenerate/check: cargo run -q -p server-api --example generate_worker_launch_api_types -- --check
|
||||||
|
|||||||
Reference in New Issue
Block a user