Merge remote-tracking branch 'origin/aether-rust-pioneer' into codex/async-cleanup-records

# Conflicts:
#	apps/aether-gateway/src/maintenance/mod.rs
#	apps/aether-gateway/src/maintenance/runtime/runners.rs
This commit is contained in:
fawney19
2026-05-10 00:28:39 +08:00
315 changed files with 19750 additions and 3183 deletions

View File

@@ -152,6 +152,11 @@ const PERMISSION_GROUPS: &[PermissionGroup] = &[
label: "系统",
assignable: true,
},
PermissionGroup {
scope: "tasks",
label: "后台任务",
assignable: true,
},
PermissionGroup {
scope: "usage",
label: "用量",
@@ -434,6 +439,9 @@ fn permission_key(scope: &str, access: &str) -> &'static str {
("system", "read") => "admin:system:read",
("system", "write") => "admin:system:write",
("system", "admin") => "admin:system:admin",
("tasks", "read") => "admin:tasks:read",
("tasks", "write") => "admin:tasks:write",
("tasks", "admin") => "admin:tasks:admin",
("usage", "read") => "admin:usage:read",
("usage", "write") => "admin:usage:write",
("usage", "admin") => "admin:usage:admin",
@@ -492,6 +500,7 @@ mod tests {
"security",
"stats",
"system",
"tasks",
"usage",
"users",
"video_tasks",

View File

@@ -46,6 +46,83 @@ pub(super) fn classify_admin_operations_family_route(
"admin:video_tasks",
false,
))
} else if method == http::Method::GET
&& matches!(normalized_path, "/api/admin/tasks" | "/api/admin/tasks/")
{
Some(classified(
"admin_proxy",
"tasks_manage",
"list_tasks",
"admin:tasks",
false,
))
} else if method == http::Method::GET
&& matches!(
normalized_path,
"/api/admin/tasks/stats" | "/api/admin/tasks/stats/"
)
{
Some(classified(
"admin_proxy",
"tasks_manage",
"stats",
"admin:tasks",
false,
))
} else if method == http::Method::GET
&& normalized_path.starts_with("/api/admin/tasks/")
&& normalized_path.ends_with("/events")
&& normalized_path.matches('/').count() == 5
{
Some(classified(
"admin_proxy",
"tasks_manage",
"events",
"admin:tasks",
false,
))
} else if method == http::Method::POST
&& normalized_path.starts_with("/api/admin/tasks/")
&& normalized_path.ends_with("/cancel")
&& normalized_path.matches('/').count() == 5
{
Some(classified(
"admin_proxy",
"tasks_manage",
"cancel",
"admin:tasks",
false,
))
} else if method == http::Method::POST
&& normalized_path.starts_with("/api/admin/tasks/")
&& normalized_path.ends_with("/trigger")
&& normalized_path.matches('/').count() == 5
{
Some(classified(
"admin_proxy",
"tasks_manage",
"trigger",
"admin:tasks",
false,
))
} else if method == http::Method::GET
&& normalized_path.starts_with("/api/admin/tasks/")
&& normalized_path["/api/admin/tasks/".len()..]
.split('/')
.count()
== 1
&& !matches!(
normalized_path,
"/api/admin/tasks/stats" | "/api/admin/tasks/stats/"
)
{
Some(classified(
"admin_proxy",
"tasks_manage",
"detail",
"admin:tasks",
false,
))
} else if method == http::Method::GET
&& normalized_path.starts_with("/api/admin/video-tasks/")
&& normalized_path.ends_with("/video")
@@ -113,6 +190,34 @@ pub(super) fn classify_admin_operations_family_route(
"admin:proxy_nodes",
false,
))
} else if method == http::Method::GET
&& matches!(
normalized_path,
"/api/admin/proxy-nodes/metrics/fleet" | "/api/admin/proxy-nodes/metrics/fleet/"
)
{
Some(classified(
"admin_proxy",
"proxy_nodes_manage",
"list_fleet_metrics",
"admin:proxy_nodes",
false,
))
} else if method == http::Method::GET
&& normalized_path_no_trailing.starts_with("/api/admin/proxy-nodes/")
&& normalized_path_no_trailing.ends_with("/metrics")
&& normalized_path_no_trailing["/api/admin/proxy-nodes/".len()..]
.split('/')
.count()
== 2
{
Some(classified(
"admin_proxy",
"proxy_nodes_manage",
"list_node_metrics",
"admin:proxy_nodes",
false,
))
} else if method == http::Method::GET
&& normalized_path_no_trailing.starts_with("/api/admin/proxy-nodes/")
&& normalized_path_no_trailing["/api/admin/proxy-nodes/".len()..]

View File

@@ -468,6 +468,20 @@ pub(super) fn classify_public_support_route(
"user:self",
false,
))
} else if method == http::Method::POST
&& has_single_nested_suffix_after_prefix(
normalized_path,
"/api/users/me/api-keys/",
"install-sessions",
)
{
Some(classified(
"public_support",
"users_me",
"api_key_install_session_create",
"user:self",
false,
))
} else if method == http::Method::POST
&& normalized_path.starts_with("/api/me/management-tokens/")
&& normalized_path.ends_with("/regenerate")
@@ -659,6 +673,17 @@ pub(super) fn classify_public_support_route(
"public:system_catalog",
false,
))
} else if method == http::Method::GET
&& (has_single_segment_after_prefix(normalized_path, "/install/")
|| has_single_segment_after_prefix(normalized_path, "/i/"))
{
Some(classified(
"public_support",
"install",
"script",
"public:install",
false,
))
} else if method == http::Method::GET && normalized_path == "/test-connection" {
Some(classified(
"public_support",

View File

@@ -100,6 +100,24 @@ fn classifies_admin_proxy_nodes_detail_as_admin_proxy_route() {
);
}
#[test]
fn classifies_admin_proxy_node_metrics_as_admin_proxy_route() {
assert_proxy_nodes_admin_route(
http::Method::GET,
"/api/admin/proxy-nodes/node-1/metrics?from=1700000000&to=1700003600&step=1m",
"list_node_metrics",
);
}
#[test]
fn classifies_admin_proxy_fleet_metrics_as_admin_proxy_route() {
assert_proxy_nodes_admin_route(
http::Method::GET,
"/api/admin/proxy-nodes/metrics/fleet?from=1700000000&to=1700003600&step=1m",
"list_fleet_metrics",
);
}
#[test]
fn classifies_admin_proxy_nodes_delete_as_admin_proxy_route() {
assert_proxy_nodes_admin_route(

View File

@@ -1,6 +1,7 @@
use http::Uri;
use super::{classify_control_route, headers};
use super::{classify_control_route, headers, GatewayPublicRequestContext};
use crate::handlers::shared::local_proxy_route_requires_buffered_body;
#[test]
fn classifies_models_list_as_public_support_route() {
@@ -366,6 +367,11 @@ fn classifies_users_me_routes_as_public_support_route() {
"/api/users/me/api-keys",
"api_keys_create",
),
(
http::Method::POST,
"/api/users/me/api-keys/key-1/install-sessions",
"api_key_install_session_create",
),
(
http::Method::PUT,
"/api/users/me/api-keys/key-1",
@@ -452,6 +458,25 @@ fn classifies_users_me_routes_as_public_support_route() {
}
}
#[test]
fn user_api_key_install_session_create_buffers_request_body() {
let headers = headers(&[]);
let uri: Uri = "/api/users/me/api-keys/key-1/install-sessions"
.parse()
.expect("uri should parse");
let decision =
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
let context = GatewayPublicRequestContext::from_request_parts(
"trace-install-session",
&http::Method::POST,
&uri,
&headers,
Some(decision),
);
assert!(local_proxy_route_requires_buffered_body(&context));
}
#[test]
fn classifies_payment_callback_as_public_support_route() {
let headers = headers(&[]);