Unify manual route inventory and retention semantics

This commit is contained in:
2026-09-18 02:47:05 +09:00
parent f967c1b392
commit 6baa313684
2 changed files with 214 additions and 83 deletions
+45 -20
View File
@@ -633,107 +633,132 @@ pub struct RemainingRuntimeRoute {
pub reason: &'static str,
}
pub const RUNTIME_ROUTE_VERIFICATION_CHALLENGE: &str =
"/v1/workspace-runtime-verification/challenge";
pub const RUNTIME_ROUTE_VERIFICATION_ACK: &str =
"/v1/workspace-runtime-verification/acknowledgement";
pub const RUNTIME_ROUTE_CONFIG_BUNDLES: &str = "/v1/config-bundles";
pub const RUNTIME_ROUTE_CONFIG_BUNDLE_AVAILABILITY: &str =
"/v1/config-bundles/{bundle_id}/availability";
pub const RUNTIME_ROUTE_WORKSPACE_PROMPT_PROJECTIONS: &str = "/v1/workspace-prompt-projections";
pub const RUNTIME_ROUTE_WORKING_DIRECTORIES: &str = "/v1/working-directories";
pub const RUNTIME_ROUTE_WORKING_DIRECTORY_REPOSITORY_ACCESS: &str =
"/v1/working-directories/repository-access";
pub const RUNTIME_ROUTE_SSH_PROBE: &str = "/v1/repositories/ssh/probe";
pub const RUNTIME_ROUTE_REPOSITORY_REFS_OBSERVE: &str = "/v1/repository-refs/observe";
pub const RUNTIME_ROUTE_WORKDIR_SESSIONS: &str =
"/v1/working-directories/{working_directory_id}/sessions";
pub const RUNTIME_ROUTE_WORKDIR_SESSION_OPERATIONS: &str =
"/v1/workdir-sessions/{session_id}/operations";
pub const RUNTIME_ROUTE_WORKDIR_SESSION: &str = "/v1/workdir-sessions/{session_id}";
pub const RUNTIME_ROUTE_WORKING_DIRECTORY: &str = "/v1/working-directories/{working_directory_id}";
pub const RUNTIME_ROUTE_PROTOCOL_WS: &str = "/v1/protocol/ws";
pub const RUNTIME_ROUTE_WORKER_PROTOCOL_WS: &str = "/v1/workers/{worker_id}/protocol/ws";
pub const RUNTIME_ROUTE_WORKER_ATTACHMENTS: &str = "/v1/workers/{worker_id}/attachments";
pub const RUNTIME_ROUTE_WORKER_ATTACHMENT: &str =
"/v1/workers/{worker_id}/attachments/{artifact_id}";
/// Intentionally out-of-contract Runtime routes. This inventory keeps manual paths visible rather
/// than letting them be mistaken for management-contract omissions.
pub const REMAINING_RUNTIME_ROUTES: &[RemainingRuntimeRoute] = &[
RemainingRuntimeRoute {
method: "POST",
path: "/v1/workspace-runtime-verification/challenge",
path: RUNTIME_ROUTE_VERIFICATION_CHALLENGE,
reason: "Workspace verification handshake",
},
RemainingRuntimeRoute {
method: "POST",
path: "/v1/workspace-runtime-verification/acknowledgement",
path: RUNTIME_ROUTE_VERIFICATION_ACK,
reason: "Workspace verification handshake",
},
RemainingRuntimeRoute {
method: "GET",
path: "/v1/config-bundles",
path: RUNTIME_ROUTE_CONFIG_BUNDLES,
reason: "Workspace Config transport",
},
RemainingRuntimeRoute {
method: "POST",
path: "/v1/config-bundles",
path: RUNTIME_ROUTE_CONFIG_BUNDLES,
reason: "Workspace Config transport",
},
RemainingRuntimeRoute {
method: "GET",
path: "/v1/config-bundles/{bundle_id}/availability",
path: RUNTIME_ROUTE_CONFIG_BUNDLE_AVAILABILITY,
reason: "Workspace Config transport",
},
RemainingRuntimeRoute {
method: "POST",
path: "/v1/workspace-prompt-projections",
path: RUNTIME_ROUTE_WORKSPACE_PROMPT_PROJECTIONS,
reason: "Workspace Config transport",
},
RemainingRuntimeRoute {
method: "GET",
path: "/v1/working-directories",
path: RUNTIME_ROUTE_WORKING_DIRECTORIES,
reason: "Workdir transport",
},
RemainingRuntimeRoute {
method: "POST",
path: "/v1/working-directories",
path: RUNTIME_ROUTE_WORKING_DIRECTORIES,
reason: "Workdir transport",
},
RemainingRuntimeRoute {
method: "POST",
path: "/v1/working-directories/repository-access",
path: RUNTIME_ROUTE_WORKING_DIRECTORY_REPOSITORY_ACCESS,
reason: "Workdir transport",
},
RemainingRuntimeRoute {
method: "POST",
path: "/v1/repositories/ssh/probe",
path: RUNTIME_ROUTE_SSH_PROBE,
reason: "Repository SSH transport",
},
RemainingRuntimeRoute {
method: "POST",
path: "/v1/repository-refs/observe",
path: RUNTIME_ROUTE_REPOSITORY_REFS_OBSERVE,
reason: "Repository observation transport",
},
RemainingRuntimeRoute {
method: "POST",
path: "/v1/working-directories/{working_directory_id}/sessions",
path: RUNTIME_ROUTE_WORKDIR_SESSIONS,
reason: "Workdir session transport",
},
RemainingRuntimeRoute {
method: "POST",
path: "/v1/workdir-sessions/{session_id}/operations",
path: RUNTIME_ROUTE_WORKDIR_SESSION_OPERATIONS,
reason: "Workdir session transport",
},
RemainingRuntimeRoute {
method: "DELETE",
path: "/v1/workdir-sessions/{session_id}",
path: RUNTIME_ROUTE_WORKDIR_SESSION,
reason: "Workdir session transport",
},
RemainingRuntimeRoute {
method: "GET",
path: "/v1/working-directories/{working_directory_id}",
path: RUNTIME_ROUTE_WORKING_DIRECTORY,
reason: "Workdir transport",
},
RemainingRuntimeRoute {
method: "DELETE",
path: "/v1/working-directories/{working_directory_id}",
path: RUNTIME_ROUTE_WORKING_DIRECTORY,
reason: "Workdir transport",
},
RemainingRuntimeRoute {
method: "GET",
path: "/v1/protocol/ws",
path: RUNTIME_ROUTE_PROTOCOL_WS,
reason: "WebSocket protocol transport (ws-server feature)",
},
RemainingRuntimeRoute {
method: "GET",
path: "/v1/workers/{worker_id}/protocol/ws",
path: RUNTIME_ROUTE_WORKER_PROTOCOL_WS,
reason: "WebSocket protocol transport (ws-server feature)",
},
RemainingRuntimeRoute {
method: "POST",
path: "/v1/workers/{worker_id}/attachments",
path: RUNTIME_ROUTE_WORKER_ATTACHMENTS,
reason: "Binary attachment transport",
},
RemainingRuntimeRoute {
method: "DELETE",
path: "/v1/workers/{worker_id}/attachments/{artifact_id}",
path: RUNTIME_ROUTE_WORKER_ATTACHMENT,
reason: "Binary attachment transport",
},
];
+169 -63
View File
@@ -27,13 +27,11 @@ use crate::retention::{
#[cfg(feature = "ws-server")]
use crate::runtime::RuntimeSubscriptionRecvError;
use crate::ssh_host_key_probe::{
SSH_HOST_KEY_PROBE_OPERATION, SSH_HOST_KEY_PROBE_PATH, SSH_KEYSCAN_TIMEOUT,
SshHostKeyProbeError, SshHostKeyProbeRequest, SshHostKeyProbeResponse,
probe_ssh_host_keys_with_program,
SSH_HOST_KEY_PROBE_OPERATION, SSH_KEYSCAN_TIMEOUT, SshHostKeyProbeError,
SshHostKeyProbeRequest, SshHostKeyProbeResponse, probe_ssh_host_keys_with_program,
};
use crate::workspace_issuer::{
RuntimeVerificationSigner, VerifiedWorkspaceCapability, WORKSPACE_VERIFICATION_ACK_PATH,
WORKSPACE_VERIFICATION_CHALLENGE_PATH, WORKSPACE_VERIFICATION_OPERATION,
RuntimeVerificationSigner, VerifiedWorkspaceCapability, WORKSPACE_VERIFICATION_OPERATION,
WorkspaceCapabilityExpectation, WorkspaceCapabilityVerifier,
WorkspaceRuntimeVerificationAcknowledgement, WorkspaceRuntimeVerificationAuthority,
WorkspaceRuntimeVerificationChallenge, WorkspaceRuntimeVerificationReceipt,
@@ -166,6 +164,18 @@ pub async fn serve_runtime_http_with_workspace_auth(
Ok(())
}
fn remaining_route(path: &'static str, methods: &[&str]) -> &'static str {
for method in methods {
assert!(
runtime_api::REMAINING_RUNTIME_ROUTES
.iter()
.any(|route| route.method == *method && route.path == path),
"handwritten Runtime route {method} {path} is missing from runtime-api inventory",
);
}
path
}
/// Build the REST router for an existing Runtime.
///
/// Handlers delegate to [`Runtime`] methods and keep Worker authority Runtime-local.
@@ -212,68 +222,95 @@ fn runtime_http_router_with_auth_and_ssh_keyscan_program(
let router = Router::new()
.route(
WORKSPACE_VERIFICATION_CHALLENGE_PATH,
remaining_route(runtime_api::RUNTIME_ROUTE_VERIFICATION_CHALLENGE, &["POST"]),
post(post_workspace_verification_challenge),
)
.route(
WORKSPACE_VERIFICATION_ACK_PATH,
remaining_route(runtime_api::RUNTIME_ROUTE_VERIFICATION_ACK, &["POST"]),
post(post_workspace_verification_acknowledgement),
)
.route(
"/v1/config-bundles",
remaining_route(runtime_api::RUNTIME_ROUTE_CONFIG_BUNDLES, &["GET", "POST"]),
get(list_config_bundles).post(store_config_bundle),
)
.route(
"/v1/config-bundles/{bundle_id}/availability",
remaining_route(
runtime_api::RUNTIME_ROUTE_CONFIG_BUNDLE_AVAILABILITY,
&["GET"],
),
get(check_config_bundle),
)
.route(
"/v1/workspace-prompt-projections",
remaining_route(
runtime_api::RUNTIME_ROUTE_WORKSPACE_PROMPT_PROJECTIONS,
&["POST"],
),
post(observe_workspace_prompt_projection),
)
.route(
"/v1/working-directories",
remaining_route(
runtime_api::RUNTIME_ROUTE_WORKING_DIRECTORIES,
&["GET", "POST"],
),
get(list_working_directories).post(create_working_directory),
)
.route(
"/v1/working-directories/repository-access",
remaining_route(
runtime_api::RUNTIME_ROUTE_WORKING_DIRECTORY_REPOSITORY_ACCESS,
&["POST"],
),
post(authorize_working_directory_repository_access),
)
.route(
SSH_HOST_KEY_PROBE_PATH,
remaining_route(runtime_api::RUNTIME_ROUTE_SSH_PROBE, &["POST"]),
post(probe_repository_ssh_host_keys),
)
.route("/v1/repository-refs/observe", post(observe_repository_ref))
.route(
"/v1/working-directories/{working_directory_id}/sessions",
remaining_route(
runtime_api::RUNTIME_ROUTE_REPOSITORY_REFS_OBSERVE,
&["POST"],
),
post(observe_repository_ref),
)
.route(
remaining_route(runtime_api::RUNTIME_ROUTE_WORKDIR_SESSIONS, &["POST"]),
post(open_workdir_session),
)
.route(
"/v1/workdir-sessions/{session_id}/operations",
remaining_route(
runtime_api::RUNTIME_ROUTE_WORKDIR_SESSION_OPERATIONS,
&["POST"],
),
post(run_workdir_session_operation),
)
.route(
"/v1/workdir-sessions/{session_id}",
remaining_route(runtime_api::RUNTIME_ROUTE_WORKDIR_SESSION, &["DELETE"]),
delete(close_workdir_session),
)
.route(
"/v1/working-directories/{working_directory_id}",
remaining_route(
runtime_api::RUNTIME_ROUTE_WORKING_DIRECTORY,
&["GET", "DELETE"],
),
get(get_working_directory).delete(cleanup_working_directory),
)
.route(
"/v1/workers/{worker_id}/attachments",
remaining_route(runtime_api::RUNTIME_ROUTE_WORKER_ATTACHMENTS, &["POST"]),
post(upload_worker_file).layer(DefaultBodyLimit::max(MAX_WORKER_FILE_UPLOAD_BYTES)),
)
.route(
"/v1/workers/{worker_id}/attachments/{artifact_id}",
remaining_route(runtime_api::RUNTIME_ROUTE_WORKER_ATTACHMENT, &["DELETE"]),
delete(delete_worker_uploaded_file),
);
#[cfg(feature = "ws-server")]
let router = router
.route("/v1/protocol/ws", get(runtime_protocol_ws))
.route(
"/v1/workers/{worker_id}/protocol/ws",
remaining_route(runtime_api::RUNTIME_ROUTE_PROTOCOL_WS, &["GET"]),
get(runtime_protocol_ws),
)
.route(
remaining_route(runtime_api::RUNTIME_ROUTE_WORKER_PROTOCOL_WS, &["GET"]),
get(worker_protocol_ws),
);
@@ -2072,8 +2109,9 @@ async fn require_runtime_auth(
};
match workspace_auth.verifier.verify(token, &expected) {
Ok(verified) => {
let is_verification = path_and_query == WORKSPACE_VERIFICATION_CHALLENGE_PATH
|| path_and_query == WORKSPACE_VERIFICATION_ACK_PATH;
let is_verification = path_and_query
== runtime_api::RUNTIME_ROUTE_VERIFICATION_CHALLENGE
|| path_and_query == runtime_api::RUNTIME_ROUTE_VERIFICATION_ACK;
if !is_verification {
let record = match workspace_auth
.verifications
@@ -2139,7 +2177,8 @@ async fn require_runtime_auth(
let workspace_bootstrap_request = request.method() == Method::POST
&& matches!(
request.uri().path(),
WORKSPACE_VERIFICATION_CHALLENGE_PATH | WORKSPACE_VERIFICATION_ACK_PATH
runtime_api::RUNTIME_ROUTE_VERIFICATION_CHALLENGE
| runtime_api::RUNTIME_ROUTE_VERIFICATION_ACK
);
if state.workspace_auth.is_some() && !workspace_bootstrap_request {
let local_token_matches = state
@@ -2211,7 +2250,8 @@ fn auth_workspace_scope(
}
fn workspace_runtime_operation(method: &Method, path: &str) -> &'static str {
if (path == WORKSPACE_VERIFICATION_CHALLENGE_PATH || path == WORKSPACE_VERIFICATION_ACK_PATH)
if (path == runtime_api::RUNTIME_ROUTE_VERIFICATION_CHALLENGE
|| path == runtime_api::RUNTIME_ROUTE_VERIFICATION_ACK)
&& *method == Method::POST
{
return WORKSPACE_VERIFICATION_OPERATION;
@@ -2238,9 +2278,9 @@ fn required_runtime_permission(method: &Method, path: &str) -> Option<&'static s
if path == "/v1/workers" && *method == Method::POST {
return Some("workers:create");
}
if (path == "/v1/working-directories/repository-access"
|| path == "/v1/repository-refs/observe"
|| path == SSH_HOST_KEY_PROBE_PATH)
if (path == runtime_api::RUNTIME_ROUTE_WORKING_DIRECTORY_REPOSITORY_ACCESS
|| path == runtime_api::RUNTIME_ROUTE_REPOSITORY_REFS_OBSERVE
|| path == runtime_api::RUNTIME_ROUTE_SSH_PROBE)
&& *method == Method::POST
{
return Some(SSH_HOST_KEY_PROBE_OPERATION);
@@ -2250,9 +2290,9 @@ fn required_runtime_permission(method: &Method, path: &str) -> Option<&'static s
{
return Some("workdirs:operate");
}
if path.starts_with("/v1/config-bundles")
|| path.starts_with("/v1/workspace-prompt-projections")
|| path.starts_with("/v1/working-directories")
if path.starts_with(runtime_api::RUNTIME_ROUTE_CONFIG_BUNDLES)
|| path.starts_with(runtime_api::RUNTIME_ROUTE_WORKSPACE_PROMPT_PROJECTIONS)
|| path.starts_with(runtime_api::RUNTIME_ROUTE_WORKING_DIRECTORIES)
{
return Some("workers:create");
}
@@ -2265,7 +2305,7 @@ fn required_runtime_permission(method: &Method, path: &str) -> Option<&'static s
if path.ends_with("/stop") || path.ends_with("/cancel") {
return Some("workers:stop");
}
if path == "/v1/protocol/ws" {
if path == runtime_api::RUNTIME_ROUTE_PROTOCOL_WS {
return Some("workers:list");
}
if path.ends_with("/protocol") || path.ends_with("/protocol/ws") {
@@ -2551,6 +2591,7 @@ mod tests {
WorkerExecutionSpawnResult,
};
use crate::management::RuntimeOptions;
use crate::retention::{DiagnosticsDisposition, SessionDisposition};
use crate::workspace_issuer::{
InMemoryWorkspaceClaimReplayProtection, InMemoryWorkspaceRuntimeVerificationAuthority,
WorkspaceCapabilityClaims, WorkspaceCapabilityVerifier, WorkspaceIssuerTrustRecord,
@@ -2629,23 +2670,23 @@ mod tests {
.map(|route| route.path)
.collect::<std::collections::BTreeSet<_>>();
for path in [
WORKSPACE_VERIFICATION_CHALLENGE_PATH,
WORKSPACE_VERIFICATION_ACK_PATH,
"/v1/config-bundles",
"/v1/config-bundles/{bundle_id}/availability",
"/v1/workspace-prompt-projections",
"/v1/working-directories",
"/v1/working-directories/repository-access",
SSH_HOST_KEY_PROBE_PATH,
"/v1/repository-refs/observe",
"/v1/working-directories/{working_directory_id}/sessions",
"/v1/workdir-sessions/{session_id}/operations",
"/v1/workdir-sessions/{session_id}",
"/v1/working-directories/{working_directory_id}",
"/v1/workers/{worker_id}/attachments",
"/v1/workers/{worker_id}/attachments/{artifact_id}",
"/v1/protocol/ws",
"/v1/workers/{worker_id}/protocol/ws",
runtime_api::RUNTIME_ROUTE_VERIFICATION_CHALLENGE,
runtime_api::RUNTIME_ROUTE_VERIFICATION_ACK,
runtime_api::RUNTIME_ROUTE_CONFIG_BUNDLES,
runtime_api::RUNTIME_ROUTE_CONFIG_BUNDLE_AVAILABILITY,
runtime_api::RUNTIME_ROUTE_WORKSPACE_PROMPT_PROJECTIONS,
runtime_api::RUNTIME_ROUTE_WORKING_DIRECTORIES,
runtime_api::RUNTIME_ROUTE_WORKING_DIRECTORY_REPOSITORY_ACCESS,
runtime_api::RUNTIME_ROUTE_SSH_PROBE,
runtime_api::RUNTIME_ROUTE_REPOSITORY_REFS_OBSERVE,
runtime_api::RUNTIME_ROUTE_WORKDIR_SESSIONS,
runtime_api::RUNTIME_ROUTE_WORKDIR_SESSION_OPERATIONS,
runtime_api::RUNTIME_ROUTE_WORKDIR_SESSION,
runtime_api::RUNTIME_ROUTE_WORKING_DIRECTORY,
runtime_api::RUNTIME_ROUTE_WORKER_ATTACHMENTS,
runtime_api::RUNTIME_ROUTE_WORKER_ATTACHMENT,
runtime_api::RUNTIME_ROUTE_PROTOCOL_WS,
runtime_api::RUNTIME_ROUTE_WORKER_PROTOCOL_WS,
] {
assert!(
inventoried.contains(path),
@@ -2722,7 +2763,7 @@ mod tests {
worker_id: None,
operation: WORKSPACE_VERIFICATION_OPERATION.to_string(),
method: "POST".to_string(),
path_and_query: WORKSPACE_VERIFICATION_CHALLENGE_PATH.to_string(),
path_and_query: runtime_api::RUNTIME_ROUTE_VERIFICATION_CHALLENGE.to_string(),
body_digest: workspace_request_body_digest(&body),
iat: unix_now_i64(),
exp: challenge.expires_at,
@@ -2736,7 +2777,7 @@ mod tests {
.oneshot(
Request::builder()
.method(Method::POST)
.uri(WORKSPACE_VERIFICATION_CHALLENGE_PATH)
.uri(runtime_api::RUNTIME_ROUTE_VERIFICATION_CHALLENGE)
.header(header::AUTHORIZATION, format!("Bearer {token}"))
.header(header::CONTENT_TYPE, "application/json")
.body(Body::from(body))
@@ -2782,7 +2823,7 @@ mod tests {
};
let acknowledgement_body = serde_json::to_vec(&acknowledgement).unwrap();
let acknowledgement_claims = WorkspaceCapabilityClaims {
path_and_query: WORKSPACE_VERIFICATION_ACK_PATH.to_string(),
path_and_query: runtime_api::RUNTIME_ROUTE_VERIFICATION_ACK.to_string(),
body_digest: workspace_request_body_digest(&acknowledgement_body),
jti: "ack-token".to_string(),
..claims
@@ -2797,7 +2838,7 @@ mod tests {
.oneshot(
Request::builder()
.method(Method::POST)
.uri(WORKSPACE_VERIFICATION_ACK_PATH)
.uri(runtime_api::RUNTIME_ROUTE_VERIFICATION_ACK)
.header(
header::AUTHORIZATION,
format!("Bearer {acknowledgement_token}"),
@@ -2815,7 +2856,7 @@ mod tests {
for authorization in [None, Some("Bearer malformed")] {
let mut request = Request::builder()
.method(Method::POST)
.uri("/v1/config-bundles");
.uri(runtime_api::RUNTIME_ROUTE_CONFIG_BUNDLES);
if let Some(authorization) = authorization {
request = request.header(header::AUTHORIZATION, authorization);
}
@@ -2841,6 +2882,7 @@ mod tests {
)
.unwrap();
let response = app
.clone()
.oneshot(
Request::builder()
.method(Method::GET)
@@ -2853,6 +2895,58 @@ mod tests {
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let retention_worker_id = task_request("retention-scope").worker_id;
let retention_path = format!("/v1/workers/{retention_worker_id}/retention/execute");
let retention_request = WorkerRetentionExecutionRequest {
operation_id: "retention-op".to_string(),
input_fingerprint: "fingerprint".to_string(),
archive_id: None,
workspace_id: "workspace-b".to_string(),
source_runtime_id: "runtime-a".to_string(),
worker_id: retention_worker_id,
expected_worker_revision: "revision".to_string(),
source_created_at: "2025-01-01T00:00:00Z".to_string(),
removed_at: "2025-01-02T00:00:00Z".to_string(),
effective_profile: None,
retention_class: None,
policy_id: "policy".to_string(),
policy_revision: 1,
session_disposition: SessionDisposition::Purge,
diagnostics_disposition: DiagnosticsDisposition::Retain,
};
let retention_body = serde_json::to_vec(&retention_request).unwrap();
let retention_claims = WorkspaceCapabilityClaims {
operation: required_runtime_permission(&Method::POST, &retention_path)
.unwrap()
.to_string(),
method: "POST".to_string(),
path_and_query: retention_path.clone(),
worker_id: Some(retention_worker_id.to_string()),
body_digest: workspace_request_body_digest(&retention_body),
jti: "retention-scope-token".to_string(),
..ping_claims
};
let retention_token = issue_workspace_capability_token(
&workspace_identity.signing_key().unwrap(),
&retention_claims,
)
.unwrap();
let response = app
.oneshot(
Request::builder()
.method(Method::POST)
.uri(retention_path)
.header(header::AUTHORIZATION, format!("Bearer {retention_token}"))
.header(header::CONTENT_TYPE, "application/json")
.body(Body::from(retention_body))
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
let error: RuntimeHttpErrorResponse = read_json(response).await;
assert_eq!(error.error.code, "worker_not_found");
}
#[test]
@@ -3010,15 +3104,21 @@ mod tests {
#[test]
fn workdir_routes_require_dedicated_operation_permission() {
assert_eq!(
required_runtime_permission(&Method::POST, "/v1/working-directories/repository-access",),
required_runtime_permission(
&Method::POST,
runtime_api::RUNTIME_ROUTE_WORKING_DIRECTORY_REPOSITORY_ACCESS,
),
Some("workdirs:operate")
);
assert_eq!(
required_runtime_permission(&Method::POST, "/v1/repository-refs/observe"),
required_runtime_permission(
&Method::POST,
runtime_api::RUNTIME_ROUTE_REPOSITORY_REFS_OBSERVE
),
Some("workdirs:operate")
);
assert_eq!(
required_runtime_permission(&Method::POST, SSH_HOST_KEY_PROBE_PATH),
required_runtime_permission(&Method::POST, runtime_api::RUNTIME_ROUTE_SSH_PROBE),
Some(SSH_HOST_KEY_PROBE_OPERATION)
);
assert_eq!(
@@ -3600,11 +3700,11 @@ mod tests {
#[test]
fn ssh_probe_uses_workdir_operation_capability() {
assert_eq!(
required_runtime_permission(&Method::POST, SSH_HOST_KEY_PROBE_PATH),
required_runtime_permission(&Method::POST, runtime_api::RUNTIME_ROUTE_SSH_PROBE),
Some(SSH_HOST_KEY_PROBE_OPERATION)
);
assert_eq!(
workspace_runtime_operation(&Method::POST, SSH_HOST_KEY_PROBE_PATH),
workspace_runtime_operation(&Method::POST, runtime_api::RUNTIME_ROUTE_SSH_PROBE),
SSH_HOST_KEY_PROBE_OPERATION
);
}
@@ -3621,7 +3721,7 @@ mod tests {
let response = authed_json_request(
app,
Method::POST,
SSH_HOST_KEY_PROBE_PATH,
runtime_api::RUNTIME_ROUTE_SSH_PROBE,
token,
&SshHostKeyProbeRequest {
hostname: "-oProxyCommand=malicious".to_string(),
@@ -3686,8 +3786,14 @@ mod tests {
.unwrap();
assert_eq!(unauthenticated.status(), StatusCode::UNAUTHORIZED);
let response =
authed_json_request(app, Method::POST, SSH_HOST_KEY_PROBE_PATH, token, &body).await;
let response = authed_json_request(
app,
Method::POST,
runtime_api::RUNTIME_ROUTE_SSH_PROBE,
token,
&body,
)
.await;
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(
std::fs::read_to_string(recorded_arguments).unwrap(),