已合并
feat:删除旧的三层匹配策略 #469
王欣创建于 8月30日
feat:删除旧的三层匹配策略 #469
已合并
共 30 个文件变更+62-6996
| @@ -5,7 +5,7 @@ | |||
| 5 | | 目录 | 说明 | 默认端口 | | 5 | | 目录 | 说明 | 默认端口 | |
| 6 | |------|------|----------| | 6 | |------|------|----------| |
| 7 | | `identity_center/` | 认证中心(OAuth2 / JWT、用户与组织) | `8770` | | 7 | | `identity_center/` | 认证中心(OAuth2 / JWT、用户与组织) | `8770` | |
| 8 | -| `manager_server/` | 管理 API(实例、模板、策略等)+ Manager WebSocket | REST `8765`、WS `8766` | | 8 | +| `manager_server/` | 管理 API(实例、模板等)+ Manager WebSocket | REST `8765`、WS `8766` | |
| 9 | | `manager_web/` | 管理面 Web 前端(React + Vite) | `5273` | | 9 | | `manager_web/` | 管理面 Web 前端(React + Vite) | `5273` | |
| 10 | 10 | ||
| 11 | 本地开发时,**先启动身份中心,再启动管理 API,最后启动前端**。 | 11 | 本地开发时,**先启动身份中心,再启动管理 API,最后启动前端**。 |
| @@ -107,7 +107,7 @@ npm run dev | |||
| 107 | 107 | ||
| 108 | | 用户名 | 密码 | 角色 | 用途 | | 108 | | 用户名 | 密码 | 角色 | 用途 | |
| 109 | |--------|------|------|------| | 109 | |--------|------|------|------| |
| 110 | -| `admin` | `admin` | 管理员 | 登录管理面(`/manager`),可进行实例、模板、策略等配置 | | 110 | +| `admin` | `admin` | 管理员 | 登录管理面(`/manager`),可进行实例、模板等配置 | |
| 111 | | `user1` | `user1` | 普通用户 | 登录用户端(`/user` 跳转至同源 `/chat/`),无管理权限 | | 111 | | `user1` | `user1` | 普通用户 | 登录用户端(`/user` 跳转至同源 `/chat/`),无管理权限 | |
| 112 | 112 | ||
| 113 | Vite 开发服务器已配置代理,`/api` → `8765`、`/idp` → `8770`(会自动去掉 `/idp` 前缀)。 | 113 | Vite 开发服务器已配置代理,`/api` → `8765`、`/idp` → `8770`(会自动去掉 `/idp` 前缀)。 |
| @@ -1,403 +0,0 @@ | |||
| 1 | -#!/usr/bin/env python3 | ||
| 2 | -# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved. | ||
| 3 | -"""验证 Gateway Runtime Management 按路由上下文加载正确的 ``service_config`` 模板。 | ||
| 4 | - | ||
| 5 | -与 ``RuntimeManagementAgentClient.send_request`` / ``send_request_stream`` 使用同一套 | ||
| 6 | -``load_effective_enterprise_config(..., slots=[service_config])`` 读库逻辑(不依赖 K8s 池)。 | ||
| 7 | -加载 ``service_config`` 槽位时,还会解析命中策略上的 ``service_id`` / ``agent_id`` / | ||
| 8 | -``workspace_dir`` 模板(如 ``${group_id}::${bot_id}``、``${user_id}``),与 Runtime 转发前 | ||
| 9 | -写入 ``AgentRequest`` 的行为一致。 | ||
| 10 | - | ||
| 11 | -前置:目标实例已创建;已用 ``enterprise_config_demo_data_config.py`` 写入演示策略 | ||
| 12 | -(含 Agent 策略 ``workspace_dir``)。数据库连接与 Gateway 进程一致,由 | ||
| 13 | -``manager_ws_client.infrastructure.db.Database`` 按 ``GATEWAY_*`` / ``.env`` 解析 | ||
| 14 | -(支持 sqlite / mysql),脚本侧无需再传数据目录。 | ||
| 15 | - | ||
| 16 | -典型用法(PowerShell,项目根目录;请在仓库根 ``.env`` 中配置与 Gateway 相同的 ``GATEWAY_*``):: | ||
| 17 | - | ||
| 18 | - # 单场景:alice → S1 销售组 AgentServer 池(2.5.1)+ workspace_dir=alice | ||
| 19 | - uv run python applications/manager/manager_server/scripts/enterprise_runtime_service_config.py \\ | ||
| 20 | - --group-id g_demo_sales --bot-id bot_main --user-id alice b26bc496-dfee-488b-a2ab-8bae8ce94985 | ||
| 21 | - | ||
| 22 | - # 一次跑完文档 3.2.1–3.2.3 三个演示场景 | ||
| 23 | - uv run python applications/manager/manager_server/scripts/enterprise_runtime_service_config.py \\ | ||
| 24 | - --provision-json provision.json --all-scenarios | ||
| 25 | - | ||
| 26 | -``--provision-json`` 可自动读取 ``jiuwenclaw_id``。 | ||
| 27 | -""" | ||
| 28 | - | ||
| 29 | -from __future__ import annotations | ||
| 30 | - | ||
| 31 | -import argparse | ||
| 32 | -import asyncio | ||
| 33 | -import hashlib | ||
| 34 | -import json | ||
| 35 | -import logging | ||
| 36 | -import sys | ||
| 37 | -from dataclasses import dataclass | ||
| 38 | -from pathlib import Path | ||
| 39 | -from typing import Any | ||
| 40 | - | ||
| 41 | -logger = logging.getLogger(__name__) | ||
| 42 | - | ||
| 43 | - | ||
| 44 | -@dataclass(frozen=True) | ||
| 45 | -class ServiceConfigExpectation: | ||
| 46 | - label: str | ||
| 47 | - template_name: str | ||
| 48 | - min_idle_services: int | ||
| 49 | - max_services: int | ||
| 50 | - policy_source: str | ||
| 51 | - service_id: str | None | ||
| 52 | - agent_id: str | None | ||
| 53 | - workspace_dir: str | None | ||
| 54 | - | ||
| 55 | - | ||
| 56 | -_DEMO_SCENARIOS: dict[tuple[str, str], ServiceConfigExpectation] = { | ||
| 57 | - ("g_demo_sales", "alice"): ServiceConfigExpectation( | ||
| 58 | - label="S1 销售组 AgentServer 池", | ||
| 59 | - template_name="销售组 AgentServer 池", | ||
| 60 | - min_idle_services=2, | ||
| 61 | - max_services=10, | ||
| 62 | - policy_source="2.5.1 / 2.6.1", | ||
| 63 | - service_id="g_demo_sales::bot_main", | ||
| 64 | - agent_id="alice", | ||
| 65 | - workspace_dir="alice", | ||
| 66 | - ), | ||
| 67 | - ("g_demo_sales", "bob"): ServiceConfigExpectation( | ||
| 68 | - label="S1 销售组 AgentServer 池", | ||
| 69 | - template_name="销售组 AgentServer 池", | ||
| 70 | - min_idle_services=2, | ||
| 71 | - max_services=10, | ||
| 72 | - policy_source="继承 2.5.1 / 2.6.2", | ||
| 73 | - service_id="g_demo_sales::bot_main", | ||
| 74 | - agent_id="default_agent_id_1", | ||
| 75 | - workspace_dir="g_demo_sales::bot_main::bob", | ||
| 76 | - ), | ||
| 77 | - ("g_unknown", "bob"): ServiceConfigExpectation( | ||
| 78 | - label="S2 全局兜底 AgentServer 池", | ||
| 79 | - template_name="全局兜底 AgentServer 池", | ||
| 80 | - min_idle_services=1, | ||
| 81 | - max_services=5, | ||
| 82 | - policy_source="2.7 全局兜底", | ||
| 83 | - service_id=None, | ||
| 84 | - agent_id=None, | ||
| 85 | - workspace_dir=None, | ||
| 86 | - ), | ||
| 87 | -} | ||
| 88 | - | ||
| 89 | -_ALL_SCENARIO_KEYS = ( | ||
| 90 | - ("g_demo_sales", "bot_main", "alice"), | ||
| 91 | - ("g_demo_sales", "bot_main", "bob"), | ||
| 92 | - ("g_unknown", "bot_main", "bob"), | ||
| 93 | -) | ||
| 94 | - | ||
| 95 | - | ||
| 96 | -def _configure_cli_logging() -> None: | ||
| 97 | - root = logging.getLogger() | ||
| 98 | - root.handlers.clear() | ||
| 99 | - root.setLevel(logging.INFO) | ||
| 100 | - fmt = logging.Formatter("%(message)s") | ||
| 101 | - out = logging.StreamHandler(sys.stdout) | ||
| 102 | - out.setLevel(logging.INFO) | ||
| 103 | - out.setFormatter(fmt) | ||
| 104 | - err = logging.StreamHandler(sys.stderr) | ||
| 105 | - err.setLevel(logging.ERROR) | ||
| 106 | - err.setFormatter(fmt) | ||
| 107 | - root.addHandler(out) | ||
| 108 | - root.addHandler(err) | ||
| 109 | - | ||
| 110 | - | ||
| 111 | -def _load_jiuwenclaw_id_from_provision(path: Path) -> str: | ||
| 112 | - raw = json.loads(path.read_text(encoding="utf-8")) | ||
| 113 | - data = raw.get("data", raw) | ||
| 114 | - if not isinstance(data, dict): | ||
| 115 | - raise ValueError(f"无法在 {path} 中找到 data 对象") | ||
| 116 | - jiuwenclaw_id = str(data.get("jiuwenclaw_id") or "").strip() | ||
| 117 | - if not jiuwenclaw_id: | ||
| 118 | - raise ValueError(f"无法在 {path} 中找到 data.jiuwenclaw_id") | ||
| 119 | - return jiuwenclaw_id | ||
| 120 | - | ||
| 121 | - | ||
| 122 | -def _bootstrap_modules() -> tuple[Any, Any, Any, Any, Any]: | ||
| 123 | - from jiuwenclaw.infrastructure.module_importer import ( | ||
| 124 | - ensure_manager_ws_client_package, | ||
| 125 | - import_manager_ws_client_module, | ||
| 126 | - ) | ||
| 127 | - | ||
| 128 | - ext_root = ensure_manager_ws_client_package() | ||
| 129 | - loader_mod = import_manager_ws_client_module("core.enterprise_config.loader") | ||
| 130 | - schemas_mod = import_manager_ws_client_module("core.enterprise_config.schemas") | ||
| 131 | - utils_mod = import_manager_ws_client_module("infrastructure.utils") | ||
| 132 | - db_mod = import_manager_ws_client_module("infrastructure.db") | ||
| 133 | - gateway_db_mod = import_manager_ws_client_module("core.enterprise_config.gateway_db") | ||
| 134 | - database = db_mod.Database(relative_root=ext_root) | ||
| 135 | - return ( | ||
| 136 | - loader_mod.load_effective_enterprise_config, | ||
| 137 | - schemas_mod.TemplateRefSlot.SERVICE_CONFIG, | ||
| 138 | - utils_mod.set_jiuwenclaw_id, | ||
| 139 | - database, | ||
| 140 | - gateway_db_mod, | ||
| 141 | - ) | ||
| 142 | - | ||
| 143 | - | ||
| 144 | -async def _ensure_gateway_database(database: Any) -> None: | ||
| 145 | - """与 Gateway / enterprise_config 读库相同:经 ``Database.ensure_ready`` 连接。""" | ||
| 146 | - await database.ensure_ready(log_prefix="enterprise_runtime_verify") | ||
| 147 | - summary = database.config_summary() | ||
| 148 | - logger.info("[db] gateway database ready: %s", summary) | ||
| 149 | - if summary.get("db_type") == "sqlite": | ||
| 150 | - sqlite_path = Path(str(summary.get("sqlite_path", ""))) | ||
| 151 | - if not sqlite_path.is_file(): | ||
| 152 | - logger.warning("[db] sqlite 文件不存在: %s", sqlite_path) | ||
| 153 | - | ||
| 154 | - | ||
| 155 | -async def _close_gateway_databases(database: Any, gateway_db_mod: Any) -> None: | ||
| 156 | - """关闭脚本与 ``enterprise_config.gateway_db`` 各自持有的连接。""" | ||
| 157 | - enterprise_db = getattr(gateway_db_mod, "_db", None) | ||
| 158 | - for label, db in ( | ||
| 159 | - ("enterprise_runtime_verify", database), | ||
| 160 | - ("enterprise_config", enterprise_db), | ||
| 161 | - ): | ||
| 162 | - if db is None: | ||
| 163 | - continue | ||
| 164 | - try: | ||
| 165 | - await db.close() | ||
| 166 | - except Exception as exc: | ||
| 167 | - logger.warning("[db] %s disconnect error: %s", label, exc) | ||
| 168 | - | ||
| 169 | - | ||
| 170 | -class _RoutingRequest: | ||
| 171 | - def __init__( | ||
| 172 | - self, | ||
| 173 | - *, | ||
| 174 | - group_id: str, | ||
| 175 | - bot_id: str, | ||
| 176 | - user_id: str, | ||
| 177 | - ) -> None: | ||
| 178 | - self.params = { | ||
| 179 | - "group_id": group_id, | ||
| 180 | - "bot_id": bot_id, | ||
| 181 | - "user_id": user_id, | ||
| 182 | - } | ||
| 183 | - | ||
| 184 | - | ||
| 185 | -def _log_expectation(group_id: str, user_id: str) -> ServiceConfigExpectation | None: | ||
| 186 | - expect = _DEMO_SCENARIOS.get((group_id, user_id)) | ||
| 187 | - if expect is None: | ||
| 188 | - expect = _DEMO_SCENARIOS.get((group_id, "bob")) | ||
| 189 | - if expect is None: | ||
| 190 | - logger.warning("[expect] 未找到演示 seed 对照表(group_id=%s user_id=%s)", group_id, user_id) | ||
| 191 | - return None | ||
| 192 | - workspace_key = ( | ||
| 193 | - hashlib.md5(expect.workspace_dir.encode("utf-8")).hexdigest() | ||
| 194 | - if expect.workspace_dir | ||
| 195 | - else None | ||
| 196 | - ) | ||
| 197 | - logger.info( | ||
| 198 | - "[expect] service_config=%s (%s); template_name=%r; min_idle=%s max_services=%s; " | ||
| 199 | - "service_id=%r agent_id=%r workspace_dir=%r workspace_key=%s path=workspace_%s/agent/…", | ||
| 200 | - expect.label, | ||
| 201 | - expect.policy_source, | ||
| 202 | - expect.template_name, | ||
| 203 | - expect.min_idle_services, | ||
| 204 | - expect.max_services, | ||
| 205 | - expect.service_id, | ||
| 206 | - expect.agent_id, | ||
| 207 | - expect.workspace_dir, | ||
| 208 | - workspace_key, | ||
| 209 | - workspace_key or "?", | ||
| 210 | - ) | ||
| 211 | - return expect | ||
| 212 | - | ||
| 213 | - | ||
| 214 | -def _entity_matches(entity: dict[str, Any], expect: ServiceConfigExpectation) -> list[str]: | ||
| 215 | - errors: list[str] = [] | ||
| 216 | - name = str(entity.get("template_name") or "") | ||
| 217 | - if name != expect.template_name: | ||
| 218 | - errors.append(f"template_name={name!r} 预期 {expect.template_name!r}") | ||
| 219 | - for field in ("min_idle_services", "max_services"): | ||
| 220 | - actual = entity.get(field) | ||
| 221 | - expected = getattr(expect, field) | ||
| 222 | - try: | ||
| 223 | - if int(actual) != int(expected): | ||
| 224 | - errors.append(f"{field}={actual} 预期 {expected}") | ||
| 225 | - except (TypeError, ValueError): | ||
| 226 | - errors.append(f"{field}={actual!r} 无法解析为整数") | ||
| 227 | - return errors | ||
| 228 | - | ||
| 229 | - | ||
| 230 | -def _policy_ids_match(loaded: Any, expect: ServiceConfigExpectation) -> list[str]: | ||
| 231 | - errors: list[str] = [] | ||
| 232 | - if loaded.service_id != expect.service_id: | ||
| 233 | - errors.append( | ||
| 234 | - f"service_id={loaded.service_id!r} 预期 {expect.service_id!r}" | ||
| 235 | - ) | ||
| 236 | - if loaded.agent_id != expect.agent_id: | ||
| 237 | - errors.append( | ||
| 238 | - f"agent_id={loaded.agent_id!r} 预期 {expect.agent_id!r}" | ||
| 239 | - ) | ||
| 240 | - actual_ws = getattr(loaded, "workspace_dir", None) | ||
| 241 | - if actual_ws != expect.workspace_dir: | ||
| 242 | - errors.append( | ||
| 243 | - f"workspace_dir={actual_ws!r} 预期 {expect.workspace_dir!r}" | ||
| 244 | - ) | ||
| 245 | - return errors | ||
| 246 | - | ||
| 247 | - | ||
| 248 | -def _workspace_key(workspace_dir: str | None) -> str | None: | ||
| 249 | - if not workspace_dir: | ||
| 250 | - return None | ||
| 251 | - return hashlib.md5(str(workspace_dir).encode("utf-8")).hexdigest() | ||
| 252 | - | ||
| 253 | - | ||
| 254 | -async def _verify_one( | ||
| 255 | - *, | ||
| 256 | - jiuwenclaw_id: str, | ||
| 257 | - group_id: str, | ||
| 258 | - bot_id: str, | ||
| 259 | - user_id: str, | ||
| 260 | - load_fn: Any, | ||
| 261 | - service_config_slot: Any, | ||
| 262 | -) -> int: | ||
| 263 | - expect = _log_expectation(group_id, user_id) | ||
| 264 | - logger.info( | ||
| 265 | - "[load] jiuwenclaw_id=%s group_id=%s bot_id=%s user_id=%s", | ||
| 266 | - jiuwenclaw_id, | ||
| 267 | - group_id, | ||
| 268 | - bot_id, | ||
| 269 | - user_id, | ||
| 270 | - ) | ||
| 271 | - | ||
| 272 | - request = _RoutingRequest(group_id=group_id, bot_id=bot_id, user_id=user_id) | ||
| 273 | - loaded = await load_fn(request, [service_config_slot]) | ||
| 274 | - if loaded is None: | ||
| 275 | - logger.error("[fail] 未加载到 service_config(策略未命中或模板不存在)") | ||
| 276 | - return 1 | ||
| 277 | - | ||
| 278 | - entities = loaded.service_config or [] | ||
| 279 | - if not entities: | ||
| 280 | - logger.error("[fail] service_config 为空 template_ref=%s", loaded.template_ref) | ||
| 281 | - return 1 | ||
| 282 | - | ||
| 283 | - entity = entities[0] | ||
| 284 | - ws_dir = getattr(loaded, "workspace_dir", None) | ||
| 285 | - ws_key = _workspace_key(ws_dir) | ||
| 286 | - logger.info( | ||
| 287 | - "[loaded] template_id=%s template_name=%s min_idle_services=%s max_services=%s " | ||
| 288 | - "service_policy_id=%s global_policy_id=%s service_id=%s agent_id=%s " | ||
| 289 | - "workspace_dir=%s workspace_key=%s path=workspace_%s/agent/…", | ||
| 290 | - entity.get("template_id"), | ||
| 291 | - entity.get("template_name"), | ||
| 292 | - entity.get("min_idle_services"), | ||
| 293 | - entity.get("max_services"), | ||
| 294 | - loaded.service_policy_id, | ||
| 295 | - loaded.global_policy_id, | ||
| 296 | - loaded.service_id, | ||
| 297 | - loaded.agent_id, | ||
| 298 | - ws_dir, | ||
| 299 | - ws_key, | ||
| 300 | - ws_key or "?", | ||
| 301 | - ) | ||
| 302 | - | ||
| 303 | - if expect is None: | ||
| 304 | - logger.info("[ok] 已加载 service_config(无演示对照表,跳过字段断言)") | ||
| 305 | - return 0 | ||
| 306 | - | ||
| 307 | - errors = _entity_matches(entity, expect) | ||
| 308 | - errors.extend(_policy_ids_match(loaded, expect)) | ||
| 309 | - if errors: | ||
| 310 | - for err in errors: | ||
| 311 | - logger.error("[fail] %s", err) | ||
| 312 | - return 1 | ||
| 313 | - | ||
| 314 | - logger.info("[ok] service_config 与演示 seed 预期一致(%s)", expect.label) | ||
| 315 | - return 0 | ||
| 316 | - | ||
| 317 | - | ||
| 318 | -async def _run(args: argparse.Namespace) -> int: | ||
| 319 | - jiuwenclaw_id = (args.jiuwenclaw_id or "").strip() | ||
| 320 | - | ||
| 321 | - if args.provision_json is not None and not jiuwenclaw_id: | ||
| 322 | - jiuwenclaw_id = _load_jiuwenclaw_id_from_provision(args.provision_json) | ||
| 323 | - | ||
| 324 | - if not jiuwenclaw_id: | ||
| 325 | - logger.error("缺少 jiuwenclaw_id( positional 或 --provision-json)") | ||
| 326 | - return 1 | ||
| 327 | - | ||
| 328 | - load_fn, service_config_slot, set_jiuwenclaw_id, database, gateway_db_mod = ( | ||
| 329 | - _bootstrap_modules() | ||
| 330 | - ) | ||
| 331 | - set_jiuwenclaw_id(jiuwenclaw_id) | ||
| 332 | - await _ensure_gateway_database(database) | ||
| 333 | - | ||
| 334 | - try: | ||
| 335 | - if args.all_scenarios: | ||
| 336 | - exit_code = 0 | ||
| 337 | - for group_id, bot_id, user_id in _ALL_SCENARIO_KEYS: | ||
| 338 | - logger.info("") | ||
| 339 | - logger.info("=== 场景 group_id=%s user_id=%s ===", group_id, user_id) | ||
| 340 | - code = await _verify_one( | ||
| 341 | - jiuwenclaw_id=jiuwenclaw_id, | ||
| 342 | - group_id=group_id, | ||
| 343 | - bot_id=bot_id, | ||
| 344 | - user_id=user_id, | ||
| 345 | - load_fn=load_fn, | ||
| 346 | - service_config_slot=service_config_slot, | ||
| 347 | - ) | ||
| 348 | - if code != 0: | ||
| 349 | - exit_code = code | ||
| 350 | - return exit_code | ||
| 351 | - | ||
| 352 | - return await _verify_one( | ||
| 353 | - jiuwenclaw_id=jiuwenclaw_id, | ||
| 354 | - group_id=args.group_id, | ||
| 355 | - bot_id=args.bot_id, | ||
| 356 | - user_id=args.user_id, | ||
| 357 | - load_fn=load_fn, | ||
| 358 | - service_config_slot=service_config_slot, | ||
| 359 | - ) | ||
| 360 | - finally: | ||
| 361 | - await _close_gateway_databases(database, gateway_db_mod) | ||
| 362 | - | ||
| 363 | - | ||
| 364 | -def _parse_args() -> argparse.Namespace: | ||
| 365 | - p = argparse.ArgumentParser( | ||
| 366 | - description="验证 Gateway Runtime Management 的 service_config / workspace_dir 企业策略加载", | ||
| 367 | - ) | ||
| 368 | - p.add_argument( | ||
| 369 | - "jiuwenclaw_id", | ||
| 370 | - nargs="?", | ||
| 371 | - default="", | ||
| 372 | - help="目标实例 jiuwenclaw_id(positional,建议放在命令末尾;可与 --provision-json 二选一)", | ||
| 373 | - ) | ||
| 374 | - p.add_argument("--group-id", default="g_demo_sales", help="企业策略 group_id") | ||
| 375 | - p.add_argument("--bot-id", default="bot_main", help="企业策略 bot_id") | ||
| 376 | - p.add_argument("--user-id", default="alice", help="企业策略 user_id") | ||
| 377 | - p.add_argument( | ||
| 378 | - "--provision-json", | ||
| 379 | - type=Path, | ||
| 380 | - help="含 jiuwenclaw_id 的 JSON 文件(读取 jiuwenclaw_id)", | ||
| 381 | - ) | ||
| 382 | - p.add_argument( | ||
| 383 | - "--all-scenarios", | ||
| 384 | - action="store_true", | ||
| 385 | - help="依次验证文档 3.2.1 alice / 3.2.2 bob / 3.2.3 g_unknown 三个 service_config / workspace_dir 场景", | ||
| 386 | - ) | ||
| 387 | - return p.parse_args() | ||
| 388 | - | ||
| 389 | - | ||
| 390 | -def main() -> int: | ||
| 391 | - _configure_cli_logging() | ||
| 392 | - args = _parse_args() | ||
| 393 | - try: | ||
| 394 | - return asyncio.run(_run(args)) | ||
| 395 | - except KeyboardInterrupt: | ||
| 396 | - return 130 | ||
| 397 | - except Exception as exc: | ||
| 398 | - logger.error("[failed] %s", exc) | ||
| 399 | - return 1 | ||
| 400 | - | ||
| 401 | - | ||
| 402 | -if __name__ == "__main__": | ||
| 403 | - raise SystemExit(main()) | ||
Dapplications/manager/manager_server/src/manager_server/core/config_effective_policy/__init__.py+0-30
| @@ -1,30 +0,0 @@ | |||
| 1 | -# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved. | ||
| 2 | -"""配置生效策略业务包。""" | ||
| 3 | - | ||
| 4 | -from manager_server.core.config_effective_policy.config_default_template_mapping import ( | ||
| 5 | - ConfigDefaultTemplateMappingService, | ||
| 6 | - push_template_mappings_sync_to_gateway, | ||
| 7 | -) | ||
| 8 | -from manager_server.core.config_effective_policy.config_effective_agent_policy import ( | ||
| 9 | - ConfigEffectiveAgentPolicyService, | ||
| 10 | - push_agent_policies_sync_to_gateway, | ||
| 11 | -) | ||
| 12 | -from manager_server.core.config_effective_policy.config_effective_global_policy import ( | ||
| 13 | - ConfigEffectiveGlobalPolicyService, | ||
| 14 | - push_global_policies_sync_to_gateway, | ||
| 15 | -) | ||
| 16 | -from manager_server.core.config_effective_policy.config_effective_service_policy import ( | ||
| 17 | - ConfigEffectiveServicePolicyService, | ||
| 18 | - push_service_policies_sync_to_gateway, | ||
| 19 | -) | ||
| 20 | - | ||
| 21 | -__all__ = ( | ||
| 22 | - "ConfigDefaultTemplateMappingService", | ||
| 23 | - "ConfigEffectiveAgentPolicyService", | ||
| 24 | - "ConfigEffectiveGlobalPolicyService", | ||
| 25 | - "ConfigEffectiveServicePolicyService", | ||
| 26 | - "push_agent_policies_sync_to_gateway", | ||
| 27 | - "push_global_policies_sync_to_gateway", | ||
| 28 | - "push_service_policies_sync_to_gateway", | ||
| 29 | - "push_template_mappings_sync_to_gateway", | ||
| 30 | -) | ||
| @@ -1,387 +0,0 @@ | |||
| 1 | -"""作用域默认模板映射 config_default_template_mapping 业务逻辑。""" | ||
| 2 | - | ||
| 3 | -from __future__ import annotations | ||
| 4 | - | ||
| 5 | -from datetime import datetime | ||
| 6 | -from typing import Any | ||
| 7 | - | ||
| 8 | -from openjiuwen_runtime.foundation.db.handler import DBHandler | ||
| 9 | - | ||
| 10 | -from manager_server.core.template.push_template_to_gateway import ( | ||
| 11 | - sync_gateway_templates_after_mapping_change, | ||
| 12 | -) | ||
| 13 | -from manager_server.infrastructure.common import ( | ||
| 14 | - DEFAULT_POLICY_ORDER_BY, | ||
| 15 | - resolve_order_by, | ||
| 16 | -) | ||
| 17 | -from manager_server.infrastructure.jiuwenclaw_id import validate_jiuwenclaw_id | ||
| 18 | -from manager_server.infrastructure.utils import ( | ||
| 19 | - iso_datetime, | ||
| 20 | - new_uuid4, | ||
| 21 | - strip_optional, | ||
| 22 | - utc_now, | ||
| 23 | -) | ||
| 24 | -from manager_server.manager_config_push import gateway_request | ||
| 25 | -from manager_server.models.config_effective_policy_models import ( | ||
| 26 | - CONFIG_DEFAULT_TEMPLATE_MAPPING_TABLE_DEF, | ||
| 27 | -) | ||
| 28 | -from manager_server.schemas.config_effective_policy_schemas import ( | ||
| 29 | - ConfigDefaultTemplateMappingCreateBody, | ||
| 30 | - ConfigDefaultTemplateMappingListQuery, | ||
| 31 | - ConfigDefaultTemplateMappingOut, | ||
| 32 | - ConfigDefaultTemplateMappingUpdateBody, | ||
| 33 | -) | ||
| 34 | -from manager_server.schemas.template_slot_schemas import MAPPING_SCOPE_TYPES | ||
| 35 | - | ||
| 36 | -_TEMPLATE_MAPPING_TABLE = CONFIG_DEFAULT_TEMPLATE_MAPPING_TABLE_DEF.table_name | ||
| 37 | -_MAPPING_PATH = "/api/v1/config-default-template-mappings" | ||
| 38 | -_LIST_ALL_CAP = 10_000 | ||
| 39 | -_PUSH_DROP_KEYS = frozenset({"id", "created_at", "updated_at", "jiuwenclaw_id"}) | ||
| 40 | -_ALLOWED_SORT_FIELDS = frozenset({ | ||
| 41 | - "policy_name", | ||
| 42 | - "policy_desc", | ||
| 43 | - "priority", | ||
| 44 | - "scope_type", | ||
| 45 | - "scope_id", | ||
| 46 | - "template_type", | ||
| 47 | - "template_id", | ||
| 48 | - "updated_at", | ||
| 49 | -}) | ||
| 50 | - | ||
| 51 | - | ||
| 52 | -def _clean_mapping_for_create(mapping: dict[str, Any]) -> dict[str, Any]: | ||
| 53 | - return {k: v for k, v in mapping.items() if k not in _PUSH_DROP_KEYS} | ||
| 54 | - | ||
| 55 | - | ||
| 56 | -def _matches_search(row: Any, query: str) -> bool: | ||
| 57 | - needle = query.strip().lower() | ||
| 58 | - if not needle: | ||
| 59 | - return True | ||
| 60 | - fields = [ | ||
| 61 | - str(getattr(row, "policy_id", "") or ""), | ||
| 62 | - str(getattr(row, "policy_name", "") or ""), | ||
| 63 | - str(getattr(row, "policy_desc", "") or ""), | ||
| 64 | - str(getattr(row, "scope_type", "") or ""), | ||
| 65 | - str(getattr(row, "scope_id", "") or ""), | ||
| 66 | - str(getattr(row, "template_type", "") or ""), | ||
| 67 | - str(getattr(row, "template_id", "") or ""), | ||
| 68 | - str(getattr(row, "priority", "") or ""), | ||
| 69 | - ] | ||
| 70 | - return any(needle in field.lower() for field in fields) | ||
| 71 | - | ||
| 72 | - | ||
| 73 | -async def push_template_mappings_sync_to_gateway( | ||
| 74 | - handler: DBHandler, | ||
| 75 | - jiuwenclaw_id: str, | ||
| 76 | -) -> dict[str, Any]: | ||
| 77 | - """Gateway 注册后:将 MDB 中该实例全部默认模板映射 bulk push 到 GDB。""" | ||
| 78 | - jid = str(jiuwenclaw_id or "").strip() | ||
| 79 | - if not jid: | ||
| 80 | - raise ValueError("jiuwenclaw_id is required") | ||
| 81 | - rows = await handler.list_records( | ||
| 82 | - _TEMPLATE_MAPPING_TABLE, | ||
| 83 | - {"jiuwenclaw_id": jid}, | ||
| 84 | - limit=_LIST_ALL_CAP, | ||
| 85 | - offset=0, | ||
| 86 | - ) | ||
| 87 | - mappings = [_row_to_out(row).model_dump(mode="json") for row in rows] | ||
| 88 | - last: dict[str, Any] | None = None | ||
| 89 | - for idx, mapping in enumerate(mappings): | ||
| 90 | - if not isinstance(mapping, dict): | ||
| 91 | - continue | ||
| 92 | - last = await gateway_request( | ||
| 93 | - jid, | ||
| 94 | - "POST", | ||
| 95 | - f"{_MAPPING_PATH}/", | ||
| 96 | - _clean_mapping_for_create(mapping), | ||
| 97 | - revision=f"sync-mapping:{idx}", | ||
| 98 | - ) | ||
| 99 | - return last or { | ||
| 100 | - "revision": "sync-mapping", | ||
| 101 | - "success_flag": True, | ||
| 102 | - "result": {"synced": 0}, | ||
| 103 | - "transport": "http", | ||
| 104 | - } | ||
| 105 | - | ||
| 106 | - | ||
| 107 | -def _mapping_pk(jiuwenclaw_id: str, mapping_id: int) -> dict[str, Any]: | ||
| 108 | - return {"jiuwenclaw_id": jiuwenclaw_id, "id": mapping_id} | ||
| 109 | - | ||
| 110 | - | ||
| 111 | -def _validate_scope(scope_type: str, scope_id: str) -> tuple[str, str]: | ||
| 112 | - st = str(scope_type or "").strip().lower() | ||
| 113 | - if st not in MAPPING_SCOPE_TYPES: | ||
| 114 | - raise ValueError( | ||
| 115 | - f"scope_type must be one of {sorted(MAPPING_SCOPE_TYPES)}, got {scope_type!r}" | ||
| 116 | - ) | ||
| 117 | - sid = str(scope_id or "").strip() | ||
| 118 | - if not sid: | ||
| 119 | - raise ValueError("scope_id is required") | ||
| 120 | - return st, sid | ||
| 121 | - | ||
| 122 | - | ||
| 123 | -def _row_to_out(row: Any) -> ConfigDefaultTemplateMappingOut: | ||
| 124 | - return ConfigDefaultTemplateMappingOut( | ||
| 125 | - id=row.id, | ||
| 126 | - jiuwenclaw_id=row.jiuwenclaw_id, | ||
| 127 | - policy_id=row.policy_id, | ||
| 128 | - policy_name=row.policy_name, | ||
| 129 | - policy_desc=row.policy_desc, | ||
| 130 | - scope_type=row.scope_type, | ||
| 131 | - scope_id=row.scope_id, | ||
| 132 | - priority=row.priority, | ||
| 133 | - template_id=row.template_id, | ||
| 134 | - template_type=row.template_type, | ||
| 135 | - enabled=row.enabled, | ||
| 136 | - data=row.data, | ||
| 137 | - created_at=iso_datetime(row.created_at), | ||
| 138 | - updated_at=iso_datetime(row.updated_at), | ||
| 139 | - ) | ||
| 140 | - | ||
| 141 | - | ||
| 142 | -class ConfigDefaultTemplateMappingService: | ||
| 143 | - def __init__(self, handler: DBHandler) -> None: | ||
| 144 | - self._handler = handler | ||
| 145 | - | ||
| 146 | - @staticmethod | ||
| 147 | - def _mapping_dict_for_push( | ||
| 148 | - row: dict[str, Any], *, now: datetime | ||
| 149 | - ) -> dict[str, Any]: | ||
| 150 | - """构建下发给 Gateway 的 mapping 对象(不含 id,由 Gateway 自增)。""" | ||
| 151 | - return { | ||
| 152 | - "jiuwenclaw_id": row["jiuwenclaw_id"], | ||
| 153 | - "policy_id": row["policy_id"], | ||
| 154 | - "policy_name": row.get("policy_name"), | ||
| 155 | - "policy_desc": row.get("policy_desc"), | ||
| 156 | - "scope_type": row.get("scope_type"), | ||
| 157 | - "scope_id": row.get("scope_id"), | ||
| 158 | - "priority": row["priority"], | ||
| 159 | - "template_id": row["template_id"], | ||
| 160 | - "template_type": row["template_type"], | ||
| 161 | - "enabled": row.get("enabled", True), | ||
| 162 | - "data": row.get("data"), | ||
| 163 | - "created_at": iso_datetime(row.get("created_at") or now), | ||
| 164 | - "updated_at": iso_datetime(row.get("updated_at") or now), | ||
| 165 | - } | ||
| 166 | - | ||
| 167 | - async def create( | ||
| 168 | - self, | ||
| 169 | - jiuwenclaw_id: str, | ||
| 170 | - body: ConfigDefaultTemplateMappingCreateBody, | ||
| 171 | - ) -> ConfigDefaultTemplateMappingOut: | ||
| 172 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 173 | - scope_type, scope_id = _validate_scope(body.scope_type, body.scope_id) | ||
| 174 | - | ||
| 175 | - now = utc_now() | ||
| 176 | - row = { | ||
| 177 | - "jiuwenclaw_id": normalized, | ||
| 178 | - "policy_id": new_uuid4(), | ||
| 179 | - "policy_name": body.policy_name, | ||
| 180 | - "policy_desc": body.policy_desc, | ||
| 181 | - "scope_type": scope_type, | ||
| 182 | - "scope_id": scope_id, | ||
| 183 | - "priority": body.priority, | ||
| 184 | - "template_id": body.template_id, | ||
| 185 | - "template_type": body.template_type, | ||
| 186 | - "enabled": body.enabled, | ||
| 187 | - "data": body.data, | ||
| 188 | - "created_at": now, | ||
| 189 | - "updated_at": now, | ||
| 190 | - } | ||
| 191 | - await sync_gateway_templates_after_mapping_change( | ||
| 192 | - self._handler, | ||
| 193 | - normalized, | ||
| 194 | - old_template_type=None, | ||
| 195 | - old_template_id=None, | ||
| 196 | - new_template_type=body.template_type, | ||
| 197 | - new_template_id=body.template_id, | ||
| 198 | - skip_runtime_update=True, | ||
| 199 | - ) | ||
| 200 | - ack = await gateway_request( | ||
| 201 | - normalized, | ||
| 202 | - "POST", | ||
| 203 | - f"{_MAPPING_PATH}/", | ||
| 204 | - _clean_mapping_for_create(self._mapping_dict_for_push(row, now=now)), | ||
| 205 | - ) | ||
| 206 | - ack_result = ack.get("result") if isinstance(ack, dict) else None | ||
| 207 | - mapping_id: int | None = None | ||
| 208 | - if isinstance(ack_result, dict): | ||
| 209 | - raw_id = ack_result.get("id") | ||
| 210 | - if raw_id is not None: | ||
| 211 | - mapping_id = int(raw_id) | ||
| 212 | - if mapping_id is None or mapping_id < 1: | ||
| 213 | - raise ValueError( | ||
| 214 | - "gateway config_default_template_mappings.create returned no id" | ||
| 215 | - ) | ||
| 216 | - | ||
| 217 | - payload = {**row, "id": mapping_id} | ||
| 218 | - created = await self._handler.create(_TEMPLATE_MAPPING_TABLE, payload) | ||
| 219 | - return _row_to_out(created) | ||
| 220 | - | ||
| 221 | - async def get( | ||
| 222 | - self, jiuwenclaw_id: str, mapping_id: int | ||
| 223 | - ) -> ConfigDefaultTemplateMappingOut | None: | ||
| 224 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 225 | - row = await self._handler.get( | ||
| 226 | - _TEMPLATE_MAPPING_TABLE, _mapping_pk(normalized, mapping_id) | ||
| 227 | - ) | ||
| 228 | - if row is None: | ||
| 229 | - return None | ||
| 230 | - return _row_to_out(row) | ||
| 231 | - | ||
| 232 | - async def list_mappings( | ||
| 233 | - self, | ||
| 234 | - jiuwenclaw_id: str, | ||
| 235 | - query: ConfigDefaultTemplateMappingListQuery, | ||
| 236 | - ) -> dict[str, Any]: | ||
| 237 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 238 | - template_type = query.template_type | ||
| 239 | - | ||
| 240 | - page = max(query.page, 1) | ||
| 241 | - page_size = min(max(query.page_size, 1), 200) | ||
| 242 | - filters: dict[str, Any] = {"jiuwenclaw_id": normalized} | ||
| 243 | - if query.scope_type: | ||
| 244 | - filters["scope_type"] = query.scope_type.strip().lower() | ||
| 245 | - if query.scope_id: | ||
| 246 | - filters["scope_id"] = strip_optional(query.scope_id) | ||
| 247 | - if template_type: | ||
| 248 | - filters["template_type"] = template_type | ||
| 249 | - if query.template_id: | ||
| 250 | - filters["template_id"] = query.template_id.strip() | ||
| 251 | - if query.enabled is not None: | ||
| 252 | - filters["enabled"] = query.enabled | ||
| 253 | - | ||
| 254 | - order_by = resolve_order_by( | ||
| 255 | - query.sort_by, | ||
| 256 | - query.sort_order, | ||
| 257 | - allowed_sort_fields=_ALLOWED_SORT_FIELDS, | ||
| 258 | - default_order_by=DEFAULT_POLICY_ORDER_BY, | ||
| 259 | - ) | ||
| 260 | - search_query = (query.search or "").strip() | ||
| 261 | - if search_query: | ||
| 262 | - rows = await self._handler.list_records( | ||
| 263 | - _TEMPLATE_MAPPING_TABLE, | ||
| 264 | - filters, | ||
| 265 | - limit=_LIST_ALL_CAP, | ||
| 266 | - offset=0, | ||
| 267 | - order_by=order_by, | ||
| 268 | - ) | ||
| 269 | - items = [ | ||
| 270 | - _row_to_out(row).model_dump(mode="json") | ||
| 271 | - for row in rows | ||
| 272 | - if _matches_search(row, search_query) | ||
| 273 | - ] | ||
| 274 | - total = len(items) | ||
| 275 | - offset = (page - 1) * page_size | ||
| 276 | - page_items = items[offset:offset + page_size] | ||
| 277 | - return { | ||
| 278 | - "items": page_items, | ||
| 279 | - "total": total, | ||
| 280 | - "page": page, | ||
| 281 | - "page_size": page_size, | ||
| 282 | - } | ||
| 283 | - | ||
| 284 | - offset = (page - 1) * page_size | ||
| 285 | - rows = await self._handler.list_records( | ||
| 286 | - _TEMPLATE_MAPPING_TABLE, | ||
| 287 | - filters, | ||
| 288 | - limit=page_size, | ||
| 289 | - offset=offset, | ||
| 290 | - order_by=order_by, | ||
| 291 | - ) | ||
| 292 | - total = await self._handler.count_records(_TEMPLATE_MAPPING_TABLE, filters) | ||
| 293 | - items = [_row_to_out(r).model_dump(mode="json") for r in rows] | ||
| 294 | - return { | ||
| 295 | - "items": items, | ||
| 296 | - "total": total, | ||
| 297 | - "page": page, | ||
| 298 | - "page_size": page_size, | ||
| 299 | - } | ||
| 300 | - | ||
| 301 | - async def update( | ||
| 302 | - self, | ||
| 303 | - jiuwenclaw_id: str, | ||
| 304 | - mapping_id: int, | ||
| 305 | - body: ConfigDefaultTemplateMappingUpdateBody, | ||
| 306 | - ) -> ConfigDefaultTemplateMappingOut | None: | ||
| 307 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 308 | - | ||
| 309 | - updates = body.model_dump(exclude_unset=True) | ||
| 310 | - if "scope_type" in updates and updates["scope_type"] is not None: | ||
| 311 | - updates["scope_type"] = str(updates["scope_type"]).strip().lower() | ||
| 312 | - if "scope_id" in updates and updates["scope_id"] is not None: | ||
| 313 | - updates["scope_id"] = str(updates["scope_id"]).strip() | ||
| 314 | - | ||
| 315 | - row = await self._handler.get( | ||
| 316 | - _TEMPLATE_MAPPING_TABLE, _mapping_pk(normalized, mapping_id) | ||
| 317 | - ) | ||
| 318 | - if row is None: | ||
| 319 | - return None | ||
| 320 | - | ||
| 321 | - merged_type = updates.get("scope_type", row.scope_type) | ||
| 322 | - merged_id = updates.get("scope_id", row.scope_id) | ||
| 323 | - _validate_scope(str(merged_type), str(merged_id)) | ||
| 324 | - | ||
| 325 | - if not updates: | ||
| 326 | - return _row_to_out(row) | ||
| 327 | - | ||
| 328 | - if "template_type" in updates or "template_id" in updates: | ||
| 329 | - await sync_gateway_templates_after_mapping_change( | ||
| 330 | - self._handler, | ||
| 331 | - normalized, | ||
| 332 | - old_template_type=str(getattr(row, "template_type", "") or ""), | ||
| 333 | - old_template_id=str(getattr(row, "template_id", "") or ""), | ||
| 334 | - new_template_type=str( | ||
| 335 | - updates.get("template_type", getattr(row, "template_type", "") or "") | ||
| 336 | - ), | ||
| 337 | - new_template_id=str( | ||
| 338 | - updates.get("template_id", getattr(row, "template_id", "") or "") | ||
| 339 | - ), | ||
| 340 | - skip_runtime_update=True, | ||
| 341 | - ) | ||
| 342 | - await gateway_request( | ||
| 343 | - normalized, | ||
| 344 | - "PATCH", | ||
| 345 | - f"{_MAPPING_PATH}/{mapping_id}", | ||
| 346 | - dict(updates), | ||
| 347 | - ) | ||
| 348 | - payload = dict(updates) | ||
| 349 | - payload["updated_at"] = utc_now() | ||
| 350 | - updated = await self._handler.update( | ||
| 351 | - _TEMPLATE_MAPPING_TABLE, | ||
| 352 | - _mapping_pk(normalized, mapping_id), | ||
| 353 | - payload, | ||
| 354 | - ) | ||
| 355 | - if updated is None: | ||
| 356 | - return None | ||
| 357 | - return _row_to_out(updated) | ||
| 358 | - | ||
| 359 | - async def delete( | ||
| 360 | - self, | ||
| 361 | - jiuwenclaw_id: str, | ||
| 362 | - mapping_id: int, | ||
| 363 | - ) -> bool: | ||
| 364 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 365 | - row = await self._handler.get( | ||
| 366 | - _TEMPLATE_MAPPING_TABLE, _mapping_pk(normalized, mapping_id) | ||
| 367 | - ) | ||
| 368 | - if row is None: | ||
| 369 | - return False | ||
| 370 | - await sync_gateway_templates_after_mapping_change( | ||
| 371 | - self._handler, | ||
| 372 | - normalized, | ||
| 373 | - old_template_type=str(getattr(row, "template_type", "") or ""), | ||
| 374 | - old_template_id=str(getattr(row, "template_id", "") or ""), | ||
| 375 | - new_template_type=None, | ||
| 376 | - new_template_id=None, | ||
| 377 | - skip_runtime_update=True, | ||
| 378 | - ) | ||
| 379 | - await gateway_request( | ||
| 380 | - normalized, | ||
| 381 | - "DELETE", | ||
| 382 | - f"{_MAPPING_PATH}/{mapping_id}", | ||
| 383 | - {}, | ||
| 384 | - ) | ||
| 385 | - return await self._handler.delete( | ||
| 386 | - _TEMPLATE_MAPPING_TABLE, _mapping_pk(normalized, mapping_id) | ||
| 387 | - ) | ||
| @@ -1,446 +0,0 @@ | |||
| 1 | -"""配置生效 Agent 层级策略 config_effective_agent_policy 业务逻辑。""" | ||
| 2 | - | ||
| 3 | -from __future__ import annotations | ||
| 4 | - | ||
| 5 | -from datetime import datetime | ||
| 6 | -from typing import Any | ||
| 7 | - | ||
| 8 | -from openjiuwen_runtime.foundation.db.handler import DBHandler | ||
| 9 | - | ||
| 10 | -from manager_server.core.template.push_template_to_gateway import ( | ||
| 11 | - sync_gateway_templates_after_template_ref_change, | ||
| 12 | -) | ||
| 13 | -from manager_server.infrastructure.common import ( | ||
| 14 | - DEFAULT_POLICY_ORDER_BY, | ||
| 15 | - resolve_order_by, | ||
| 16 | -) | ||
| 17 | -from manager_server.infrastructure.jiuwenclaw_id import validate_jiuwenclaw_id | ||
| 18 | -from manager_server.infrastructure.utils import ( | ||
| 19 | - iso_datetime, | ||
| 20 | - new_uuid4, | ||
| 21 | - utc_now, | ||
| 22 | -) | ||
| 23 | -from manager_server.manager_config_push import gateway_request | ||
| 24 | -from manager_server.models.config_effective_policy_models import ( | ||
| 25 | - CONFIG_EFFECTIVE_AGENT_POLICY_TABLE_DEF, | ||
| 26 | - CONFIG_EFFECTIVE_SERVICE_POLICY_TABLE_DEF, | ||
| 27 | -) | ||
| 28 | -from manager_server.infrastructure.template_ref import ( | ||
| 29 | - apply_template_ref_to_updates, | ||
| 30 | - normalize_template_ref, | ||
| 31 | - read_template_ref_from_row, | ||
| 32 | - validate_single_value_template_ref_slots, | ||
| 33 | -) | ||
| 34 | -from manager_server.schemas.config_effective_policy_schemas import ( | ||
| 35 | - ConfigEffectiveAgentPolicyCreateBody, | ||
| 36 | - ConfigEffectiveAgentPolicyListQuery, | ||
| 37 | - ConfigEffectiveAgentPolicyOut, | ||
| 38 | - ConfigEffectiveAgentPolicyUpdateBody, | ||
| 39 | -) | ||
| 40 | - | ||
| 41 | -_AGENT_POLICY_TABLE = CONFIG_EFFECTIVE_AGENT_POLICY_TABLE_DEF.table_name | ||
| 42 | -_SERVICE_POLICY_TABLE = CONFIG_EFFECTIVE_SERVICE_POLICY_TABLE_DEF.table_name | ||
| 43 | -_AGENT_POLICY_PATH = "/api/v1/config-effective/agent-policies" | ||
| 44 | -_LIST_ALL_CAP = 10_000 | ||
| 45 | -_PUSH_DROP_KEYS = frozenset({"id", "created_at", "updated_at", "jiuwenclaw_id"}) | ||
| 46 | -_ALLOWED_SORT_FIELDS = frozenset({ | ||
| 47 | - "policy_name", | ||
| 48 | - "policy_desc", | ||
| 49 | - "service_policy_id", | ||
| 50 | - "priority", | ||
| 51 | - "match_expr", | ||
| 52 | - "agent_id", | ||
| 53 | - "workspace_dir", | ||
| 54 | - "updated_at", | ||
| 55 | -}) | ||
| 56 | - | ||
| 57 | - | ||
| 58 | -def _clean_policy_for_create(policy: dict[str, Any]) -> dict[str, Any]: | ||
| 59 | - return {k: v for k, v in policy.items() if k not in _PUSH_DROP_KEYS} | ||
| 60 | - | ||
| 61 | - | ||
| 62 | -def _matches_search( | ||
| 63 | - row: Any, | ||
| 64 | - query: str, | ||
| 65 | - *, | ||
| 66 | - service_policy_names: dict[str, str], | ||
| 67 | -) -> bool: | ||
| 68 | - needle = query.strip().lower() | ||
| 69 | - if not needle: | ||
| 70 | - return True | ||
| 71 | - fields = [ | ||
| 72 | - str(getattr(row, "policy_id", "") or ""), | ||
| 73 | - str(getattr(row, "policy_name", "") or ""), | ||
| 74 | - str(getattr(row, "policy_desc", "") or ""), | ||
| 75 | - str(getattr(row, "agent_id", "") or ""), | ||
| 76 | - str(getattr(row, "workspace_dir", "") or ""), | ||
| 77 | - str(getattr(row, "service_policy_id", "") or ""), | ||
| 78 | - str(getattr(row, "priority", "") or ""), | ||
| 79 | - str(getattr(row, "match_expr", "") or ""), | ||
| 80 | - ] | ||
| 81 | - linked_service_name = service_policy_names.get( | ||
| 82 | - str(getattr(row, "service_policy_id", "") or ""), | ||
| 83 | - "", | ||
| 84 | - ) | ||
| 85 | - if linked_service_name: | ||
| 86 | - fields.append(linked_service_name) | ||
| 87 | - return any(needle in field.lower() for field in fields) | ||
| 88 | - | ||
| 89 | - | ||
| 90 | -async def _service_policy_names_by_id(handler: DBHandler, jiuwenclaw_id: str) -> dict[str, str]: | ||
| 91 | - rows = await handler.list_records( | ||
| 92 | - _SERVICE_POLICY_TABLE, | ||
| 93 | - {"jiuwenclaw_id": jiuwenclaw_id}, | ||
| 94 | - limit=_LIST_ALL_CAP, | ||
| 95 | - offset=0, | ||
| 96 | - ) | ||
| 97 | - return { | ||
| 98 | - str(getattr(row, "policy_id", "") or ""): str(getattr(row, "policy_name", "") or "") | ||
| 99 | - for row in rows | ||
| 100 | - } | ||
| 101 | - | ||
| 102 | - | ||
| 103 | -async def push_agent_policies_sync_to_gateway( | ||
| 104 | - handler: DBHandler, | ||
| 105 | - jiuwenclaw_id: str, | ||
| 106 | -) -> dict[str, Any]: | ||
| 107 | - """Gateway 注册后:将 MDB 中该实例全部 Agent 策略 bulk push 到 GDB。""" | ||
| 108 | - jid = str(jiuwenclaw_id or "").strip() | ||
| 109 | - if not jid: | ||
| 110 | - raise ValueError("jiuwenclaw_id is required") | ||
| 111 | - rows = await handler.list_records( | ||
| 112 | - _AGENT_POLICY_TABLE, | ||
| 113 | - {"jiuwenclaw_id": jid}, | ||
| 114 | - limit=_LIST_ALL_CAP, | ||
| 115 | - offset=0, | ||
| 116 | - ) | ||
| 117 | - policies = [_row_to_out(row).model_dump(mode="json") for row in rows] | ||
| 118 | - last: dict[str, Any] | None = None | ||
| 119 | - for idx, policy in enumerate(policies): | ||
| 120 | - if not isinstance(policy, dict): | ||
| 121 | - continue | ||
| 122 | - last = await gateway_request( | ||
| 123 | - jid, | ||
| 124 | - "POST", | ||
| 125 | - f"{_AGENT_POLICY_PATH}/", | ||
| 126 | - _clean_policy_for_create(policy), | ||
| 127 | - revision=f"sync-agent-policy:{idx}", | ||
| 128 | - ) | ||
| 129 | - return last or { | ||
| 130 | - "revision": "sync-agent-policy", | ||
| 131 | - "success_flag": True, | ||
| 132 | - "result": {"synced": 0}, | ||
| 133 | - "transport": "http", | ||
| 134 | - } | ||
| 135 | - | ||
| 136 | - | ||
| 137 | -def _agent_policy_pk(jiuwenclaw_id: str, policy_id: int) -> dict[str, Any]: | ||
| 138 | - return {"jiuwenclaw_id": jiuwenclaw_id, "id": policy_id} | ||
| 139 | - | ||
| 140 | - | ||
| 141 | -def _row_to_out(row: Any) -> ConfigEffectiveAgentPolicyOut: | ||
| 142 | - return ConfigEffectiveAgentPolicyOut( | ||
| 143 | - id=row.id, | ||
| 144 | - jiuwenclaw_id=row.jiuwenclaw_id, | ||
| 145 | - policy_id=row.policy_id, | ||
| 146 | - policy_name=row.policy_name, | ||
| 147 | - policy_desc=row.policy_desc, | ||
| 148 | - agent_id=row.agent_id, | ||
| 149 | - workspace_dir=row.workspace_dir, | ||
| 150 | - service_policy_id=row.service_policy_id, | ||
| 151 | - priority=row.priority, | ||
| 152 | - match_expr=row.match_expr, | ||
| 153 | - template_ref=read_template_ref_from_row(row), | ||
| 154 | - send_file_allowed=bool(getattr(row, "send_file_allowed", True)), | ||
| 155 | - enabled=row.enabled, | ||
| 156 | - data=row.data, | ||
| 157 | - created_at=iso_datetime(row.created_at), | ||
| 158 | - updated_at=iso_datetime(row.updated_at), | ||
| 159 | - ) | ||
| 160 | - | ||
| 161 | - | ||
| 162 | -class ConfigEffectiveAgentPolicyService: | ||
| 163 | - def __init__(self, handler: DBHandler) -> None: | ||
| 164 | - self._handler = handler | ||
| 165 | - | ||
| 166 | - async def _validate_parent_refs( | ||
| 167 | - self, | ||
| 168 | - *, | ||
| 169 | - jiuwenclaw_id: str, | ||
| 170 | - service_policy_id: str, | ||
| 171 | - ) -> None: | ||
| 172 | - normalized_id = service_policy_id.strip() | ||
| 173 | - if not normalized_id: | ||
| 174 | - raise ValueError("service_policy_id is required") | ||
| 175 | - sp = await self._handler.get( | ||
| 176 | - _SERVICE_POLICY_TABLE, | ||
| 177 | - {"jiuwenclaw_id": jiuwenclaw_id.strip(), "policy_id": normalized_id}, | ||
| 178 | - ) | ||
| 179 | - if sp is None: | ||
| 180 | - raise ValueError(f"unknown service_policy_id={normalized_id!r}") | ||
| 181 | - | ||
| 182 | - @staticmethod | ||
| 183 | - def _policy_dict_for_push(row: dict[str, Any], *, now: datetime) -> dict[str, Any]: | ||
| 184 | - """构建下发给 Gateway 的 policy 对象(不含 id,由 Gateway 自增)。""" | ||
| 185 | - return { | ||
| 186 | - "jiuwenclaw_id": row["jiuwenclaw_id"], | ||
| 187 | - "policy_id": row["policy_id"], | ||
| 188 | - "policy_name": row.get("policy_name"), | ||
| 189 | - "policy_desc": row.get("policy_desc"), | ||
| 190 | - "agent_id": row["agent_id"], | ||
| 191 | - "workspace_dir": row.get("workspace_dir"), | ||
| 192 | - "service_policy_id": row["service_policy_id"], | ||
| 193 | - "priority": row.get("priority", 0), | ||
| 194 | - "match_expr": row.get("match_expr"), | ||
| 195 | - "template_ref": normalize_template_ref(row.get("template_ref")), | ||
| 196 | - "send_file_allowed": bool(row.get("send_file_allowed", True)), | ||
| 197 | - "enabled": row.get("enabled", True), | ||
| 198 | - "data": row.get("data"), | ||
| 199 | - "created_at": iso_datetime(row.get("created_at") or now), | ||
| 200 | - "updated_at": iso_datetime(row.get("updated_at") or now), | ||
| 201 | - } | ||
| 202 | - | ||
| 203 | - async def create( | ||
| 204 | - self, | ||
| 205 | - jiuwenclaw_id: str, | ||
| 206 | - body: ConfigEffectiveAgentPolicyCreateBody, | ||
| 207 | - ) -> ConfigEffectiveAgentPolicyOut: | ||
| 208 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 209 | - await self._validate_parent_refs( | ||
| 210 | - jiuwenclaw_id=normalized, | ||
| 211 | - service_policy_id=body.service_policy_id, | ||
| 212 | - ) | ||
| 213 | - | ||
| 214 | - now = utc_now() | ||
| 215 | - template_ref = normalize_template_ref(body.template_ref) | ||
| 216 | - validate_single_value_template_ref_slots(template_ref) | ||
| 217 | - workspace_dir = body.workspace_dir.strip() if body.workspace_dir else None | ||
| 218 | - row = { | ||
| 219 | - "jiuwenclaw_id": normalized, | ||
| 220 | - "policy_id": new_uuid4(), | ||
| 221 | - "policy_name": body.policy_name, | ||
| 222 | - "policy_desc": body.policy_desc, | ||
| 223 | - "agent_id": body.agent_id.strip(), | ||
| 224 | - "workspace_dir": workspace_dir, | ||
| 225 | - "service_policy_id": body.service_policy_id.strip(), | ||
| 226 | - "priority": body.priority, | ||
| 227 | - "match_expr": body.match_expr, | ||
| 228 | - "template_ref": template_ref, | ||
| 229 | - "send_file_allowed": body.send_file_allowed, | ||
| 230 | - "enabled": body.enabled, | ||
| 231 | - "data": body.data, | ||
| 232 | - "created_at": now, | ||
| 233 | - "updated_at": now, | ||
| 234 | - } | ||
| 235 | - await sync_gateway_templates_after_template_ref_change( | ||
| 236 | - self._handler, | ||
| 237 | - normalized, | ||
| 238 | - old_template_ref={}, | ||
| 239 | - new_template_ref=row["template_ref"], | ||
| 240 | - skip_runtime_update=True, | ||
| 241 | - ) | ||
| 242 | - ack = await gateway_request( | ||
| 243 | - normalized, | ||
| 244 | - "POST", | ||
| 245 | - f"{_AGENT_POLICY_PATH}/", | ||
| 246 | - _clean_policy_for_create(self._policy_dict_for_push(row, now=now)), | ||
| 247 | - ) | ||
| 248 | - ack_result = ack.get("result") if isinstance(ack, dict) else None | ||
| 249 | - row_id: int | None = None | ||
| 250 | - if isinstance(ack_result, dict): | ||
| 251 | - raw_id = ack_result.get("id") | ||
| 252 | - if raw_id is not None: | ||
| 253 | - row_id = int(raw_id) | ||
| 254 | - if row_id is None or row_id < 1: | ||
| 255 | - raise ValueError( | ||
| 256 | - "gateway config_effective_agent_policies.create returned no id" | ||
| 257 | - ) | ||
| 258 | - | ||
| 259 | - payload = {**row, "id": row_id} | ||
| 260 | - created = await self._handler.create(_AGENT_POLICY_TABLE, payload) | ||
| 261 | - return _row_to_out(created) | ||
| 262 | - | ||
| 263 | - async def get( | ||
| 264 | - self, jiuwenclaw_id: str, policy_id: int | ||
| 265 | - ) -> ConfigEffectiveAgentPolicyOut | None: | ||
| 266 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 267 | - row = await self._handler.get( | ||
| 268 | - _AGENT_POLICY_TABLE, _agent_policy_pk(normalized, policy_id) | ||
| 269 | - ) | ||
| 270 | - if row is None: | ||
| 271 | - return None | ||
| 272 | - return _row_to_out(row) | ||
| 273 | - | ||
| 274 | - async def list_policies( | ||
| 275 | - self, | ||
| 276 | - jiuwenclaw_id: str, | ||
| 277 | - query: ConfigEffectiveAgentPolicyListQuery, | ||
| 278 | - ) -> dict[str, Any]: | ||
| 279 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 280 | - page = max(query.page, 1) | ||
| 281 | - page_size = min(max(query.page_size, 1), 200) | ||
| 282 | - filters: dict[str, Any] = {"jiuwenclaw_id": normalized} | ||
| 283 | - if query.service_policy_id is not None: | ||
| 284 | - filters["service_policy_id"] = query.service_policy_id.strip() | ||
| 285 | - if query.enabled is not None: | ||
| 286 | - filters["enabled"] = query.enabled | ||
| 287 | - if query.send_file_allowed is not None: | ||
| 288 | - filters["send_file_allowed"] = query.send_file_allowed | ||
| 289 | - | ||
| 290 | - order_by = resolve_order_by( | ||
| 291 | - query.sort_by, | ||
| 292 | - query.sort_order, | ||
| 293 | - allowed_sort_fields=_ALLOWED_SORT_FIELDS, | ||
| 294 | - default_order_by=DEFAULT_POLICY_ORDER_BY, | ||
| 295 | - ) | ||
| 296 | - search_query = (query.search or "").strip() | ||
| 297 | - | ||
| 298 | - service_policy_names: dict[str, str] = {} | ||
| 299 | - if search_query: | ||
| 300 | - service_policy_names = await _service_policy_names_by_id( | ||
| 301 | - self._handler, normalized | ||
| 302 | - ) | ||
| 303 | - | ||
| 304 | - if search_query: | ||
| 305 | - rows = await self._handler.list_records( | ||
| 306 | - _AGENT_POLICY_TABLE, | ||
| 307 | - filters, | ||
| 308 | - limit=_LIST_ALL_CAP, | ||
| 309 | - offset=0, | ||
| 310 | - order_by=order_by, | ||
| 311 | - ) | ||
| 312 | - matched_rows = [] | ||
| 313 | - for row in rows: | ||
| 314 | - if _matches_search( | ||
| 315 | - row, | ||
| 316 | - search_query, | ||
| 317 | - service_policy_names=service_policy_names, | ||
| 318 | - ): | ||
| 319 | - matched_rows.append(row) | ||
| 320 | - rows = matched_rows | ||
| 321 | - total = len(rows) | ||
| 322 | - offset = (page - 1) * page_size | ||
| 323 | - page_rows = rows[offset:offset + page_size] | ||
| 324 | - items = [_row_to_out(row).model_dump(mode="json") for row in page_rows] | ||
| 325 | - return { | ||
| 326 | - "items": items, | ||
| 327 | - "total": total, | ||
| 328 | - "page": page, | ||
| 329 | - "page_size": page_size, | ||
| 330 | - } | ||
| 331 | - | ||
| 332 | - offset = (page - 1) * page_size | ||
| 333 | - rows = await self._handler.list_records( | ||
| 334 | - _AGENT_POLICY_TABLE, | ||
| 335 | - filters, | ||
| 336 | - limit=page_size, | ||
| 337 | - offset=offset, | ||
| 338 | - order_by=order_by, | ||
| 339 | - ) | ||
| 340 | - total = await self._handler.count_records(_AGENT_POLICY_TABLE, filters) | ||
| 341 | - items = [_row_to_out(r).model_dump(mode="json") for r in rows] | ||
| 342 | - return { | ||
| 343 | - "items": items, | ||
| 344 | - "total": total, | ||
| 345 | - "page": page, | ||
| 346 | - "page_size": page_size, | ||
| 347 | - } | ||
| 348 | - | ||
| 349 | - async def update( | ||
| 350 | - self, | ||
| 351 | - jiuwenclaw_id: str, | ||
| 352 | - policy_id: int, | ||
| 353 | - body: ConfigEffectiveAgentPolicyUpdateBody, | ||
| 354 | - ) -> ConfigEffectiveAgentPolicyOut | None: | ||
| 355 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 356 | - | ||
| 357 | - updates = body.model_dump(exclude_unset=True) | ||
| 358 | - row = await self._handler.get( | ||
| 359 | - _AGENT_POLICY_TABLE, _agent_policy_pk(normalized, policy_id) | ||
| 360 | - ) | ||
| 361 | - if row is None: | ||
| 362 | - return None | ||
| 363 | - | ||
| 364 | - next_jiuwenclaw_id = row.jiuwenclaw_id | ||
| 365 | - if "agent_id" in updates and updates["agent_id"] is not None: | ||
| 366 | - updates["agent_id"] = updates["agent_id"].strip() | ||
| 367 | - if not updates["agent_id"]: | ||
| 368 | - raise ValueError("agent_id cannot be empty") | ||
| 369 | - | ||
| 370 | - if "workspace_dir" in updates: | ||
| 371 | - raw_ws = updates["workspace_dir"] | ||
| 372 | - updates["workspace_dir"] = ( | ||
| 373 | - raw_ws.strip() if isinstance(raw_ws, str) and raw_ws.strip() else None | ||
| 374 | - ) | ||
| 375 | - | ||
| 376 | - if "service_policy_id" in updates and updates["service_policy_id"] is not None: | ||
| 377 | - updates["service_policy_id"] = str(updates["service_policy_id"]).strip() | ||
| 378 | - if not updates["service_policy_id"]: | ||
| 379 | - raise ValueError("service_policy_id cannot be empty") | ||
| 380 | - | ||
| 381 | - next_service_policy_id = updates.get("service_policy_id", row.service_policy_id) | ||
| 382 | - if "service_policy_id" in updates: | ||
| 383 | - await self._validate_parent_refs( | ||
| 384 | - jiuwenclaw_id=next_jiuwenclaw_id, | ||
| 385 | - service_policy_id=next_service_policy_id, | ||
| 386 | - ) | ||
| 387 | - | ||
| 388 | - if not updates: | ||
| 389 | - return _row_to_out(row) | ||
| 390 | - | ||
| 391 | - old_template_ref = read_template_ref_from_row(row) | ||
| 392 | - updates = apply_template_ref_to_updates(updates, existing_row=row) | ||
| 393 | - | ||
| 394 | - if "template_ref" in updates: | ||
| 395 | - validate_single_value_template_ref_slots(updates["template_ref"]) | ||
| 396 | - await sync_gateway_templates_after_template_ref_change( | ||
| 397 | - self._handler, | ||
| 398 | - normalized, | ||
| 399 | - old_template_ref=old_template_ref, | ||
| 400 | - new_template_ref=updates["template_ref"], | ||
| 401 | - skip_runtime_update=True, | ||
| 402 | - ) | ||
| 403 | - await gateway_request( | ||
| 404 | - normalized, | ||
| 405 | - "PATCH", | ||
| 406 | - f"{_AGENT_POLICY_PATH}/{policy_id}", | ||
| 407 | - dict(updates), | ||
| 408 | - ) | ||
| 409 | - payload = dict(updates) | ||
| 410 | - payload["updated_at"] = utc_now() | ||
| 411 | - updated = await self._handler.update( | ||
| 412 | - _AGENT_POLICY_TABLE, | ||
| 413 | - _agent_policy_pk(normalized, policy_id), | ||
| 414 | - payload, | ||
| 415 | - ) | ||
| 416 | - if updated is None: | ||
| 417 | - return None | ||
| 418 | - return _row_to_out(updated) | ||
| 419 | - | ||
| 420 | - async def delete( | ||
| 421 | - self, | ||
| 422 | - jiuwenclaw_id: str, | ||
| 423 | - policy_id: int, | ||
| 424 | - ) -> bool: | ||
| 425 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 426 | - row = await self._handler.get( | ||
| 427 | - _AGENT_POLICY_TABLE, _agent_policy_pk(normalized, policy_id) | ||
| 428 | - ) | ||
| 429 | - if row is None: | ||
| 430 | - return False | ||
| 431 | - await sync_gateway_templates_after_template_ref_change( | ||
| 432 | - self._handler, | ||
| 433 | - normalized, | ||
| 434 | - old_template_ref=read_template_ref_from_row(row), | ||
| 435 | - new_template_ref={}, | ||
| 436 | - skip_runtime_update=True, | ||
| 437 | - ) | ||
| 438 | - await gateway_request( | ||
| 439 | - normalized, | ||
| 440 | - "DELETE", | ||
| 441 | - f"{_AGENT_POLICY_PATH}/{policy_id}", | ||
| 442 | - {}, | ||
| 443 | - ) | ||
| 444 | - return await self._handler.delete( | ||
| 445 | - _AGENT_POLICY_TABLE, _agent_policy_pk(normalized, policy_id) | ||
| 446 | - ) | ||
| @@ -1,329 +0,0 @@ | |||
| 1 | -"""配置生效全局兜底策略 config_effective_global_policy 业务逻辑。""" | ||
| 2 | - | ||
| 3 | -from __future__ import annotations | ||
| 4 | - | ||
| 5 | -from datetime import datetime | ||
| 6 | -from typing import Any | ||
| 7 | - | ||
| 8 | -from openjiuwen_runtime.foundation.db.handler import DBHandler | ||
| 9 | - | ||
| 10 | -from manager_server.core.template.push_template_to_gateway import ( | ||
| 11 | - sync_gateway_templates_after_template_ref_change, | ||
| 12 | -) | ||
| 13 | -from manager_server.infrastructure.common import ( | ||
| 14 | - DEFAULT_POLICY_ORDER_BY, | ||
| 15 | - resolve_order_by, | ||
| 16 | -) | ||
| 17 | -from manager_server.infrastructure.jiuwenclaw_id import validate_jiuwenclaw_id | ||
| 18 | -from manager_server.infrastructure.utils import ( | ||
| 19 | - iso_datetime, | ||
| 20 | - new_uuid4, | ||
| 21 | - utc_now, | ||
| 22 | -) | ||
| 23 | -from manager_server.manager_config_push import gateway_request | ||
| 24 | -from manager_server.models.config_effective_policy_models import ( | ||
| 25 | - CONFIG_EFFECTIVE_GLOBAL_POLICY_TABLE_DEF, | ||
| 26 | -) | ||
| 27 | -from manager_server.infrastructure.template_ref import ( | ||
| 28 | - apply_template_ref_to_updates, | ||
| 29 | - normalize_template_ref, | ||
| 30 | - read_template_ref_from_row, | ||
| 31 | - validate_single_value_template_ref_slots, | ||
| 32 | -) | ||
| 33 | -from manager_server.schemas.config_effective_policy_schemas import ( | ||
| 34 | - ConfigEffectiveGlobalPolicyCreateBody, | ||
| 35 | - ConfigEffectiveGlobalPolicyListQuery, | ||
| 36 | - ConfigEffectiveGlobalPolicyOut, | ||
| 37 | - ConfigEffectiveGlobalPolicyUpdateBody, | ||
| 38 | -) | ||
| 39 | - | ||
| 40 | -_GLOBAL_POLICY_TABLE = CONFIG_EFFECTIVE_GLOBAL_POLICY_TABLE_DEF.table_name | ||
| 41 | -_GLOBAL_POLICY_PATH = "/api/v1/config-effective/global-policies" | ||
| 42 | -_LIST_ALL_CAP = 10_000 | ||
| 43 | -_PUSH_DROP_KEYS = frozenset({"id", "created_at", "updated_at", "jiuwenclaw_id"}) | ||
| 44 | -_ALLOWED_SORT_FIELDS = frozenset({"policy_name", "policy_desc", "priority", "updated_at"}) | ||
| 45 | - | ||
| 46 | - | ||
| 47 | -def _clean_policy_for_create(policy: dict[str, Any]) -> dict[str, Any]: | ||
| 48 | - return {k: v for k, v in policy.items() if k not in _PUSH_DROP_KEYS} | ||
| 49 | - | ||
| 50 | - | ||
| 51 | -def _matches_search(row: Any, query: str) -> bool: | ||
| 52 | - needle = query.strip().lower() | ||
| 53 | - if not needle: | ||
| 54 | - return True | ||
| 55 | - fields = [ | ||
| 56 | - str(getattr(row, "policy_id", "") or ""), | ||
| 57 | - str(getattr(row, "policy_name", "") or ""), | ||
| 58 | - str(getattr(row, "policy_desc", "") or ""), | ||
| 59 | - str(getattr(row, "priority", "") or ""), | ||
| 60 | - ] | ||
| 61 | - return any(needle in field.lower() for field in fields) | ||
| 62 | - | ||
| 63 | - | ||
| 64 | -async def push_global_policies_sync_to_gateway( | ||
| 65 | - handler: DBHandler, | ||
| 66 | - jiuwenclaw_id: str, | ||
| 67 | -) -> dict[str, Any]: | ||
| 68 | - """Gateway 注册后:将 MDB 中该实例全部 Global 策略 bulk push 到 GDB。""" | ||
| 69 | - jid = str(jiuwenclaw_id or "").strip() | ||
| 70 | - if not jid: | ||
| 71 | - raise ValueError("jiuwenclaw_id is required") | ||
| 72 | - rows = await handler.list_records( | ||
| 73 | - _GLOBAL_POLICY_TABLE, | ||
| 74 | - {"jiuwenclaw_id": jid}, | ||
| 75 | - limit=_LIST_ALL_CAP, | ||
| 76 | - offset=0, | ||
| 77 | - ) | ||
| 78 | - policies = [_row_to_out(row).model_dump(mode="json") for row in rows] | ||
| 79 | - last: dict[str, Any] | None = None | ||
| 80 | - for idx, policy in enumerate(policies): | ||
| 81 | - if not isinstance(policy, dict): | ||
| 82 | - continue | ||
| 83 | - last = await gateway_request( | ||
| 84 | - jid, | ||
| 85 | - "POST", | ||
| 86 | - f"{_GLOBAL_POLICY_PATH}/", | ||
| 87 | - _clean_policy_for_create(policy), | ||
| 88 | - revision=f"sync-global-policy:{idx}", | ||
| 89 | - ) | ||
| 90 | - return last or { | ||
| 91 | - "revision": "sync-global-policy", | ||
| 92 | - "success_flag": True, | ||
| 93 | - "result": {"synced": 0}, | ||
| 94 | - "transport": "http", | ||
| 95 | - } | ||
| 96 | - | ||
| 97 | - | ||
| 98 | -def _global_policy_pk(jiuwenclaw_id: str, policy_id: int) -> dict[str, Any]: | ||
| 99 | - return {"jiuwenclaw_id": jiuwenclaw_id, "id": policy_id} | ||
| 100 | - | ||
| 101 | - | ||
| 102 | -def _row_to_out(row: Any) -> ConfigEffectiveGlobalPolicyOut: | ||
| 103 | - return ConfigEffectiveGlobalPolicyOut( | ||
| 104 | - id=row.id, | ||
| 105 | - jiuwenclaw_id=row.jiuwenclaw_id, | ||
| 106 | - policy_id=row.policy_id, | ||
| 107 | - policy_name=row.policy_name, | ||
| 108 | - policy_desc=row.policy_desc, | ||
| 109 | - priority=row.priority, | ||
| 110 | - template_ref=read_template_ref_from_row(row), | ||
| 111 | - enabled=row.enabled, | ||
| 112 | - data=row.data, | ||
| 113 | - created_at=iso_datetime(row.created_at), | ||
| 114 | - updated_at=iso_datetime(row.updated_at), | ||
| 115 | - ) | ||
| 116 | - | ||
| 117 | - | ||
| 118 | -class ConfigEffectiveGlobalPolicyService: | ||
| 119 | - def __init__(self, handler: DBHandler) -> None: | ||
| 120 | - self._handler = handler | ||
| 121 | - | ||
| 122 | - @staticmethod | ||
| 123 | - def _policy_dict_for_push(row: dict[str, Any], *, now: datetime) -> dict[str, Any]: | ||
| 124 | - """构建下发给 Gateway 的 policy 对象(不含 id,由 Gateway 自增)。""" | ||
| 125 | - return { | ||
| 126 | - "jiuwenclaw_id": row["jiuwenclaw_id"], | ||
| 127 | - "policy_id": row["policy_id"], | ||
| 128 | - "policy_name": row.get("policy_name"), | ||
| 129 | - "policy_desc": row.get("policy_desc"), | ||
| 130 | - "priority": row["priority"], | ||
| 131 | - "template_ref": normalize_template_ref(row.get("template_ref")), | ||
| 132 | - "enabled": row.get("enabled", True), | ||
| 133 | - "data": row.get("data"), | ||
| 134 | - "created_at": iso_datetime(row.get("created_at") or now), | ||
| 135 | - "updated_at": iso_datetime(row.get("updated_at") or now), | ||
| 136 | - } | ||
| 137 | - | ||
| 138 | - async def create( | ||
| 139 | - self, | ||
| 140 | - jiuwenclaw_id: str, | ||
| 141 | - body: ConfigEffectiveGlobalPolicyCreateBody, | ||
| 142 | - ) -> ConfigEffectiveGlobalPolicyOut: | ||
| 143 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 144 | - | ||
| 145 | - now = utc_now() | ||
| 146 | - template_ref = normalize_template_ref(body.template_ref) | ||
| 147 | - validate_single_value_template_ref_slots(template_ref) | ||
| 148 | - row = { | ||
| 149 | - "jiuwenclaw_id": normalized, | ||
| 150 | - "policy_id": new_uuid4(), | ||
| 151 | - "policy_name": body.policy_name, | ||
| 152 | - "policy_desc": body.policy_desc, | ||
| 153 | - "priority": body.priority, | ||
| 154 | - "template_ref": template_ref, | ||
| 155 | - "enabled": body.enabled, | ||
| 156 | - "data": body.data, | ||
| 157 | - "created_at": now, | ||
| 158 | - "updated_at": now, | ||
| 159 | - } | ||
| 160 | - await sync_gateway_templates_after_template_ref_change( | ||
| 161 | - self._handler, | ||
| 162 | - normalized, | ||
| 163 | - old_template_ref={}, | ||
| 164 | - new_template_ref=row["template_ref"], | ||
| 165 | - skip_runtime_update=True, | ||
| 166 | - ) | ||
| 167 | - ack = await gateway_request( | ||
| 168 | - normalized, | ||
| 169 | - "POST", | ||
| 170 | - f"{_GLOBAL_POLICY_PATH}/", | ||
| 171 | - _clean_policy_for_create(self._policy_dict_for_push(row, now=now)), | ||
| 172 | - ) | ||
| 173 | - ack_result = ack.get("result") if isinstance(ack, dict) else None | ||
| 174 | - row_id: int | None = None | ||
| 175 | - if isinstance(ack_result, dict): | ||
| 176 | - raw_id = ack_result.get("id") | ||
| 177 | - if raw_id is not None: | ||
| 178 | - row_id = int(raw_id) | ||
| 179 | - if row_id is None or row_id < 1: | ||
| 180 | - raise ValueError( | ||
| 181 | - "gateway config_effective_global_policies.create returned no id" | ||
| 182 | - ) | ||
| 183 | - | ||
| 184 | - payload = {**row, "id": row_id} | ||
| 185 | - created = await self._handler.create(_GLOBAL_POLICY_TABLE, payload) | ||
| 186 | - return _row_to_out(created) | ||
| 187 | - | ||
| 188 | - async def get( | ||
| 189 | - self, jiuwenclaw_id: str, policy_id: int | ||
| 190 | - ) -> ConfigEffectiveGlobalPolicyOut | None: | ||
| 191 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 192 | - row = await self._handler.get( | ||
| 193 | - _GLOBAL_POLICY_TABLE, _global_policy_pk(normalized, policy_id) | ||
| 194 | - ) | ||
| 195 | - if row is None: | ||
| 196 | - return None | ||
| 197 | - return _row_to_out(row) | ||
| 198 | - | ||
| 199 | - async def list_policies( | ||
| 200 | - self, | ||
| 201 | - jiuwenclaw_id: str, | ||
| 202 | - query: ConfigEffectiveGlobalPolicyListQuery, | ||
| 203 | - ) -> dict[str, Any]: | ||
| 204 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 205 | - page = max(query.page, 1) | ||
| 206 | - page_size = min(max(query.page_size, 1), 200) | ||
| 207 | - filters: dict[str, Any] = {"jiuwenclaw_id": normalized} | ||
| 208 | - if query.enabled is not None: | ||
| 209 | - filters["enabled"] = query.enabled | ||
| 210 | - order_by = resolve_order_by( | ||
| 211 | - query.sort_by, | ||
| 212 | - query.sort_order, | ||
| 213 | - allowed_sort_fields=_ALLOWED_SORT_FIELDS, | ||
| 214 | - default_order_by=DEFAULT_POLICY_ORDER_BY, | ||
| 215 | - ) | ||
| 216 | - | ||
| 217 | - search_query = (query.search or "").strip() | ||
| 218 | - if search_query: | ||
| 219 | - rows = await self._handler.list_records( | ||
| 220 | - _GLOBAL_POLICY_TABLE, | ||
| 221 | - filters, | ||
| 222 | - limit=_LIST_ALL_CAP, | ||
| 223 | - offset=0, | ||
| 224 | - order_by=order_by, | ||
| 225 | - ) | ||
| 226 | - rows = [row for row in rows if _matches_search(row, search_query)] | ||
| 227 | - total = len(rows) | ||
| 228 | - offset = (page - 1) * page_size | ||
| 229 | - page_rows = rows[offset:offset + page_size] | ||
| 230 | - items = [_row_to_out(row).model_dump(mode="json") for row in page_rows] | ||
| 231 | - return { | ||
| 232 | - "items": items, | ||
| 233 | - "total": total, | ||
| 234 | - "page": page, | ||
| 235 | - "page_size": page_size, | ||
| 236 | - } | ||
| 237 | - | ||
| 238 | - offset = (page - 1) * page_size | ||
| 239 | - rows = await self._handler.list_records( | ||
| 240 | - _GLOBAL_POLICY_TABLE, | ||
| 241 | - filters, | ||
| 242 | - limit=page_size, | ||
| 243 | - offset=offset, | ||
| 244 | - order_by=order_by, | ||
| 245 | - ) | ||
| 246 | - total = await self._handler.count_records(_GLOBAL_POLICY_TABLE, filters) | ||
| 247 | - items = [_row_to_out(r).model_dump(mode="json") for r in rows] | ||
| 248 | - return { | ||
| 249 | - "items": items, | ||
| 250 | - "total": total, | ||
| 251 | - "page": page, | ||
| 252 | - "page_size": page_size, | ||
| 253 | - } | ||
| 254 | - | ||
| 255 | - async def update( | ||
| 256 | - self, | ||
| 257 | - jiuwenclaw_id: str, | ||
| 258 | - policy_id: int, | ||
| 259 | - body: ConfigEffectiveGlobalPolicyUpdateBody, | ||
| 260 | - ) -> ConfigEffectiveGlobalPolicyOut | None: | ||
| 261 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 262 | - | ||
| 263 | - updates = body.model_dump(exclude_unset=True) | ||
| 264 | - | ||
| 265 | - row = await self._handler.get( | ||
| 266 | - _GLOBAL_POLICY_TABLE, _global_policy_pk(normalized, policy_id) | ||
| 267 | - ) | ||
| 268 | - if row is None: | ||
| 269 | - return None | ||
| 270 | - | ||
| 271 | - if not updates: | ||
| 272 | - return _row_to_out(row) | ||
| 273 | - | ||
| 274 | - old_template_ref = read_template_ref_from_row(row) | ||
| 275 | - updates = apply_template_ref_to_updates(updates, existing_row=row) | ||
| 276 | - | ||
| 277 | - if "template_ref" in updates: | ||
| 278 | - validate_single_value_template_ref_slots(updates["template_ref"]) | ||
| 279 | - await sync_gateway_templates_after_template_ref_change( | ||
| 280 | - self._handler, | ||
| 281 | - normalized, | ||
| 282 | - old_template_ref=old_template_ref, | ||
| 283 | - new_template_ref=updates["template_ref"], | ||
| 284 | - skip_runtime_update=True, | ||
| 285 | - ) | ||
| 286 | - await gateway_request( | ||
| 287 | - normalized, | ||
| 288 | - "PATCH", | ||
| 289 | - f"{_GLOBAL_POLICY_PATH}/{policy_id}", | ||
| 290 | - dict(updates), | ||
| 291 | - ) | ||
| 292 | - payload = dict(updates) | ||
| 293 | - payload["updated_at"] = utc_now() | ||
| 294 | - updated = await self._handler.update( | ||
| 295 | - _GLOBAL_POLICY_TABLE, | ||
| 296 | - _global_policy_pk(normalized, policy_id), | ||
| 297 | - payload, | ||
| 298 | - ) | ||
| 299 | - if updated is None: | ||
| 300 | - return None | ||
| 301 | - return _row_to_out(updated) | ||
| 302 | - | ||
| 303 | - async def delete( | ||
| 304 | - self, | ||
| 305 | - jiuwenclaw_id: str, | ||
| 306 | - policy_id: int, | ||
| 307 | - ) -> bool: | ||
| 308 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 309 | - row = await self._handler.get( | ||
| 310 | - _GLOBAL_POLICY_TABLE, _global_policy_pk(normalized, policy_id) | ||
| 311 | - ) | ||
| 312 | - if row is None: | ||
| 313 | - return False | ||
| 314 | - await sync_gateway_templates_after_template_ref_change( | ||
| 315 | - self._handler, | ||
| 316 | - normalized, | ||
| 317 | - old_template_ref=read_template_ref_from_row(row), | ||
| 318 | - new_template_ref={}, | ||
| 319 | - skip_runtime_update=True, | ||
| 320 | - ) | ||
| 321 | - await gateway_request( | ||
| 322 | - normalized, | ||
| 323 | - "DELETE", | ||
| 324 | - f"{_GLOBAL_POLICY_PATH}/{policy_id}", | ||
| 325 | - {}, | ||
| 326 | - ) | ||
| 327 | - return await self._handler.delete( | ||
| 328 | - _GLOBAL_POLICY_TABLE, _global_policy_pk(normalized, policy_id) | ||
| 329 | - ) | ||
| @@ -1,358 +0,0 @@ | |||
| 1 | -"""配置生效 Service 层级策略 config_effective_service_policy 业务逻辑。""" | ||
| 2 | - | ||
| 3 | -from __future__ import annotations | ||
| 4 | - | ||
| 5 | -from datetime import datetime | ||
| 6 | -from typing import Any | ||
| 7 | - | ||
| 8 | -from openjiuwen_runtime.foundation.db.handler import DBHandler | ||
| 9 | - | ||
| 10 | -from manager_server.core.template.push_template_to_gateway import ( | ||
| 11 | - sync_gateway_templates_after_template_ref_change, | ||
| 12 | -) | ||
| 13 | -from manager_server.infrastructure.common import ( | ||
| 14 | - DEFAULT_POLICY_ORDER_BY, | ||
| 15 | - resolve_order_by, | ||
| 16 | -) | ||
| 17 | -from manager_server.infrastructure.jiuwenclaw_id import validate_jiuwenclaw_id | ||
| 18 | -from manager_server.infrastructure.utils import ( | ||
| 19 | - iso_datetime, | ||
| 20 | - new_uuid4, | ||
| 21 | - utc_now, | ||
| 22 | -) | ||
| 23 | -from manager_server.manager_config_push import gateway_request | ||
| 24 | -from manager_server.models.config_effective_policy_models import ( | ||
| 25 | - CONFIG_EFFECTIVE_AGENT_POLICY_TABLE_DEF, | ||
| 26 | - CONFIG_EFFECTIVE_SERVICE_POLICY_TABLE_DEF, | ||
| 27 | -) | ||
| 28 | -from manager_server.infrastructure.template_ref import ( | ||
| 29 | - apply_template_ref_to_updates, | ||
| 30 | - normalize_template_ref, | ||
| 31 | - read_template_ref_from_row, | ||
| 32 | - validate_single_value_template_ref_slots, | ||
| 33 | -) | ||
| 34 | -from manager_server.schemas.config_effective_policy_schemas import ( | ||
| 35 | - ConfigEffectiveServicePolicyCreateBody, | ||
| 36 | - ConfigEffectiveServicePolicyListQuery, | ||
| 37 | - ConfigEffectiveServicePolicyOut, | ||
| 38 | - ConfigEffectiveServicePolicyUpdateBody, | ||
| 39 | -) | ||
| 40 | - | ||
| 41 | -_SERVICE_POLICY_TABLE = CONFIG_EFFECTIVE_SERVICE_POLICY_TABLE_DEF.table_name | ||
| 42 | -_AGENT_POLICY_TABLE = CONFIG_EFFECTIVE_AGENT_POLICY_TABLE_DEF.table_name | ||
| 43 | -_SERVICE_POLICY_PATH = "/api/v1/config-effective/service-policies" | ||
| 44 | -_LIST_ALL_CAP = 10_000 | ||
| 45 | -_PUSH_DROP_KEYS = frozenset({"id", "created_at", "updated_at", "jiuwenclaw_id"}) | ||
| 46 | -_ALLOWED_SORT_FIELDS = frozenset({ | ||
| 47 | - "policy_name", | ||
| 48 | - "policy_desc", | ||
| 49 | - "priority", | ||
| 50 | - "match_expr", | ||
| 51 | - "service_id", | ||
| 52 | - "updated_at", | ||
| 53 | -}) | ||
| 54 | - | ||
| 55 | - | ||
| 56 | -def _clean_policy_for_create(policy: dict[str, Any]) -> dict[str, Any]: | ||
| 57 | - return {k: v for k, v in policy.items() if k not in _PUSH_DROP_KEYS} | ||
| 58 | - | ||
| 59 | - | ||
| 60 | -def _matches_search(row: Any, query: str) -> bool: | ||
| 61 | - needle = query.strip().lower() | ||
| 62 | - if not needle: | ||
| 63 | - return True | ||
| 64 | - fields = [ | ||
| 65 | - str(getattr(row, "policy_id", "") or ""), | ||
| 66 | - str(getattr(row, "policy_name", "") or ""), | ||
| 67 | - str(getattr(row, "policy_desc", "") or ""), | ||
| 68 | - str(getattr(row, "service_id", "") or ""), | ||
| 69 | - str(getattr(row, "priority", "") or ""), | ||
| 70 | - str(getattr(row, "match_expr", "") or ""), | ||
| 71 | - ] | ||
| 72 | - return any(needle in field.lower() for field in fields) | ||
| 73 | - | ||
| 74 | - | ||
| 75 | -async def push_service_policies_sync_to_gateway( | ||
| 76 | - handler: DBHandler, | ||
| 77 | - jiuwenclaw_id: str, | ||
| 78 | -) -> dict[str, Any]: | ||
| 79 | - """Gateway 注册后:将 MDB 中该实例全部 Service 策略 bulk push 到 GDB。""" | ||
| 80 | - jid = str(jiuwenclaw_id or "").strip() | ||
| 81 | - if not jid: | ||
| 82 | - raise ValueError("jiuwenclaw_id is required") | ||
| 83 | - rows = await handler.list_records( | ||
| 84 | - _SERVICE_POLICY_TABLE, | ||
| 85 | - {"jiuwenclaw_id": jid}, | ||
| 86 | - limit=_LIST_ALL_CAP, | ||
| 87 | - offset=0, | ||
| 88 | - ) | ||
| 89 | - policies = [_row_to_out(row).model_dump(mode="json") for row in rows] | ||
| 90 | - last: dict[str, Any] | None = None | ||
| 91 | - for idx, policy in enumerate(policies): | ||
| 92 | - if not isinstance(policy, dict): | ||
| 93 | - continue | ||
| 94 | - last = await gateway_request( | ||
| 95 | - jid, | ||
| 96 | - "POST", | ||
| 97 | - f"{_SERVICE_POLICY_PATH}/", | ||
| 98 | - _clean_policy_for_create(policy), | ||
| 99 | - revision=f"sync-service-policy:{idx}", | ||
| 100 | - ) | ||
| 101 | - return last or { | ||
| 102 | - "revision": "sync-service-policy", | ||
| 103 | - "success_flag": True, | ||
| 104 | - "result": {"synced": 0}, | ||
| 105 | - "transport": "http", | ||
| 106 | - } | ||
| 107 | - | ||
| 108 | - | ||
| 109 | -def _service_policy_pk(jiuwenclaw_id: str, policy_id: int) -> dict[str, Any]: | ||
| 110 | - return {"jiuwenclaw_id": jiuwenclaw_id, "id": policy_id} | ||
| 111 | - | ||
| 112 | - | ||
| 113 | -def _row_to_out(row: Any) -> ConfigEffectiveServicePolicyOut: | ||
| 114 | - return ConfigEffectiveServicePolicyOut( | ||
| 115 | - id=row.id, | ||
| 116 | - jiuwenclaw_id=row.jiuwenclaw_id, | ||
| 117 | - policy_id=row.policy_id, | ||
| 118 | - policy_name=row.policy_name, | ||
| 119 | - policy_desc=row.policy_desc, | ||
| 120 | - service_id=row.service_id, | ||
| 121 | - priority=row.priority, | ||
| 122 | - match_expr=row.match_expr, | ||
| 123 | - template_ref=read_template_ref_from_row(row), | ||
| 124 | - enabled=row.enabled, | ||
| 125 | - data=row.data, | ||
| 126 | - created_at=iso_datetime(row.created_at), | ||
| 127 | - updated_at=iso_datetime(row.updated_at), | ||
| 128 | - ) | ||
| 129 | - | ||
| 130 | - | ||
| 131 | -class ConfigEffectiveServicePolicyService: | ||
| 132 | - def __init__(self, handler: DBHandler) -> None: | ||
| 133 | - self._handler = handler | ||
| 134 | - | ||
| 135 | - @staticmethod | ||
| 136 | - def _policy_dict_for_push(row: dict[str, Any], *, now: datetime) -> dict[str, Any]: | ||
| 137 | - """构建下发给 Gateway 的 policy 对象(不含 id,由 Gateway 自增)。""" | ||
| 138 | - return { | ||
| 139 | - "jiuwenclaw_id": row["jiuwenclaw_id"], | ||
| 140 | - "policy_id": row["policy_id"], | ||
| 141 | - "policy_name": row.get("policy_name"), | ||
| 142 | - "policy_desc": row.get("policy_desc"), | ||
| 143 | - "service_id": row["service_id"], | ||
| 144 | - "priority": row["priority"], | ||
| 145 | - "match_expr": row.get("match_expr"), | ||
| 146 | - "template_ref": normalize_template_ref(row.get("template_ref")), | ||
| 147 | - "enabled": row.get("enabled", True), | ||
| 148 | - "data": row.get("data"), | ||
| 149 | - "created_at": iso_datetime(row.get("created_at") or now), | ||
| 150 | - "updated_at": iso_datetime(row.get("updated_at") or now), | ||
| 151 | - } | ||
| 152 | - | ||
| 153 | - async def create( | ||
| 154 | - self, | ||
| 155 | - jiuwenclaw_id: str, | ||
| 156 | - body: ConfigEffectiveServicePolicyCreateBody, | ||
| 157 | - ) -> ConfigEffectiveServicePolicyOut: | ||
| 158 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 159 | - | ||
| 160 | - now = utc_now() | ||
| 161 | - template_ref = normalize_template_ref(body.template_ref) | ||
| 162 | - validate_single_value_template_ref_slots(template_ref) | ||
| 163 | - row = { | ||
| 164 | - "jiuwenclaw_id": normalized, | ||
| 165 | - "policy_id": new_uuid4(), | ||
| 166 | - "policy_name": body.policy_name, | ||
| 167 | - "policy_desc": body.policy_desc, | ||
| 168 | - "service_id": body.service_id.strip(), | ||
| 169 | - "priority": body.priority, | ||
| 170 | - "match_expr": body.match_expr, | ||
| 171 | - "template_ref": template_ref, | ||
| 172 | - "enabled": body.enabled, | ||
| 173 | - "data": body.data, | ||
| 174 | - "created_at": now, | ||
| 175 | - "updated_at": now, | ||
| 176 | - } | ||
| 177 | - await sync_gateway_templates_after_template_ref_change( | ||
| 178 | - self._handler, | ||
| 179 | - normalized, | ||
| 180 | - old_template_ref={}, | ||
| 181 | - new_template_ref=row["template_ref"], | ||
| 182 | - skip_runtime_update=True, | ||
| 183 | - ) | ||
| 184 | - ack = await gateway_request( | ||
| 185 | - normalized, | ||
| 186 | - "POST", | ||
| 187 | - f"{_SERVICE_POLICY_PATH}/", | ||
| 188 | - _clean_policy_for_create(self._policy_dict_for_push(row, now=now)), | ||
| 189 | - ) | ||
| 190 | - ack_result = ack.get("result") if isinstance(ack, dict) else None | ||
| 191 | - row_id: int | None = None | ||
| 192 | - if isinstance(ack_result, dict): | ||
| 193 | - raw_id = ack_result.get("id") | ||
| 194 | - if raw_id is not None: | ||
| 195 | - row_id = int(raw_id) | ||
| 196 | - if row_id is None or row_id < 1: | ||
| 197 | - raise ValueError( | ||
| 198 | - "gateway config_effective_service_policies.create returned no id" | ||
| 199 | - ) | ||
| 200 | - | ||
| 201 | - payload = {**row, "id": row_id} | ||
| 202 | - created = await self._handler.create(_SERVICE_POLICY_TABLE, payload) | ||
| 203 | - return _row_to_out(created) | ||
| 204 | - | ||
| 205 | - async def get( | ||
| 206 | - self, jiuwenclaw_id: str, policy_id: int | ||
| 207 | - ) -> ConfigEffectiveServicePolicyOut | None: | ||
| 208 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 209 | - row = await self._handler.get( | ||
| 210 | - _SERVICE_POLICY_TABLE, _service_policy_pk(normalized, policy_id) | ||
| 211 | - ) | ||
| 212 | - if row is None: | ||
| 213 | - return None | ||
| 214 | - return _row_to_out(row) | ||
| 215 | - | ||
| 216 | - async def list_policies( | ||
| 217 | - self, | ||
| 218 | - jiuwenclaw_id: str, | ||
| 219 | - query: ConfigEffectiveServicePolicyListQuery, | ||
| 220 | - ) -> dict[str, Any]: | ||
| 221 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 222 | - page = max(query.page, 1) | ||
| 223 | - page_size = min(max(query.page_size, 1), 200) | ||
| 224 | - filters: dict[str, Any] = {"jiuwenclaw_id": normalized} | ||
| 225 | - if query.enabled is not None: | ||
| 226 | - filters["enabled"] = query.enabled | ||
| 227 | - order_by = resolve_order_by( | ||
| 228 | - query.sort_by, | ||
| 229 | - query.sort_order, | ||
| 230 | - allowed_sort_fields=_ALLOWED_SORT_FIELDS, | ||
| 231 | - default_order_by=DEFAULT_POLICY_ORDER_BY, | ||
| 232 | - ) | ||
| 233 | - | ||
| 234 | - search_query = (query.search or "").strip() | ||
| 235 | - if search_query: | ||
| 236 | - rows = await self._handler.list_records( | ||
| 237 | - _SERVICE_POLICY_TABLE, | ||
| 238 | - filters, | ||
| 239 | - limit=_LIST_ALL_CAP, | ||
| 240 | - offset=0, | ||
| 241 | - order_by=order_by, | ||
| 242 | - ) | ||
| 243 | - rows = [row for row in rows if _matches_search(row, search_query)] | ||
| 244 | - total = len(rows) | ||
| 245 | - offset = (page - 1) * page_size | ||
| 246 | - page_rows = rows[offset:offset + page_size] | ||
| 247 | - items = [_row_to_out(row).model_dump(mode="json") for row in page_rows] | ||
| 248 | - return { | ||
| 249 | - "items": items, | ||
| 250 | - "total": total, | ||
| 251 | - "page": page, | ||
| 252 | - "page_size": page_size, | ||
| 253 | - } | ||
| 254 | - | ||
| 255 | - offset = (page - 1) * page_size | ||
| 256 | - rows = await self._handler.list_records( | ||
| 257 | - _SERVICE_POLICY_TABLE, | ||
| 258 | - filters, | ||
| 259 | - limit=page_size, | ||
| 260 | - offset=offset, | ||
| 261 | - order_by=order_by, | ||
| 262 | - ) | ||
| 263 | - total = await self._handler.count_records(_SERVICE_POLICY_TABLE, filters) | ||
| 264 | - items = [_row_to_out(r).model_dump(mode="json") for r in rows] | ||
| 265 | - return { | ||
| 266 | - "items": items, | ||
| 267 | - "total": total, | ||
| 268 | - "page": page, | ||
| 269 | - "page_size": page_size, | ||
| 270 | - } | ||
| 271 | - | ||
| 272 | - async def update( | ||
| 273 | - self, | ||
| 274 | - jiuwenclaw_id: str, | ||
| 275 | - policy_id: int, | ||
| 276 | - body: ConfigEffectiveServicePolicyUpdateBody, | ||
| 277 | - ) -> ConfigEffectiveServicePolicyOut | None: | ||
| 278 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 279 | - | ||
| 280 | - updates = body.model_dump(exclude_unset=True) | ||
| 281 | - if "service_id" in updates and updates["service_id"] is not None: | ||
| 282 | - updates["service_id"] = updates["service_id"].strip() | ||
| 283 | - if not updates["service_id"]: | ||
| 284 | - raise ValueError("service_id cannot be empty") | ||
| 285 | - | ||
| 286 | - row = await self._handler.get( | ||
| 287 | - _SERVICE_POLICY_TABLE, _service_policy_pk(normalized, policy_id) | ||
| 288 | - ) | ||
| 289 | - if row is None: | ||
| 290 | - return None | ||
| 291 | - | ||
| 292 | - if not updates: | ||
| 293 | - return _row_to_out(row) | ||
| 294 | - | ||
| 295 | - old_template_ref = read_template_ref_from_row(row) | ||
| 296 | - updates = apply_template_ref_to_updates(updates, existing_row=row) | ||
| 297 | - | ||
| 298 | - if "template_ref" in updates: | ||
| 299 | - validate_single_value_template_ref_slots(updates["template_ref"]) | ||
| 300 | - await sync_gateway_templates_after_template_ref_change( | ||
| 301 | - self._handler, | ||
| 302 | - normalized, | ||
| 303 | - old_template_ref=old_template_ref, | ||
| 304 | - new_template_ref=updates["template_ref"], | ||
| 305 | - skip_runtime_update=True, | ||
| 306 | - ) | ||
| 307 | - await gateway_request( | ||
| 308 | - normalized, | ||
| 309 | - "PATCH", | ||
| 310 | - f"{_SERVICE_POLICY_PATH}/{policy_id}", | ||
| 311 | - dict(updates), | ||
| 312 | - ) | ||
| 313 | - payload = dict(updates) | ||
| 314 | - payload["updated_at"] = utc_now() | ||
| 315 | - updated = await self._handler.update( | ||
| 316 | - _SERVICE_POLICY_TABLE, | ||
| 317 | - _service_policy_pk(normalized, policy_id), | ||
| 318 | - payload, | ||
| 319 | - ) | ||
| 320 | - if updated is None: | ||
| 321 | - return None | ||
| 322 | - return _row_to_out(updated) | ||
| 323 | - | ||
| 324 | - async def delete( | ||
| 325 | - self, | ||
| 326 | - jiuwenclaw_id: str, | ||
| 327 | - policy_id: int, | ||
| 328 | - ) -> bool: | ||
| 329 | - normalized = await validate_jiuwenclaw_id(self._handler, jiuwenclaw_id) | ||
| 330 | - row = await self._handler.get( | ||
| 331 | - _SERVICE_POLICY_TABLE, _service_policy_pk(normalized, policy_id) | ||
| 332 | - ) | ||
| 333 | - if row is None: | ||
| 334 | - return False | ||
| 335 | - linked_count = await self._handler.count_records( | ||
| 336 | - _AGENT_POLICY_TABLE, | ||
| 337 | - {"jiuwenclaw_id": normalized, "service_policy_id": row.policy_id}, | ||
| 338 | - ) | ||
| 339 | - if linked_count > 0: | ||
| 340 | - raise ValueError( | ||
| 341 | - f"cannot delete service policy: {linked_count} linked agent policies exist" | ||
| 342 | - ) | ||
| 343 | - await sync_gateway_templates_after_template_ref_change( | ||
| 344 | - self._handler, | ||
| 345 | - normalized, | ||
| 346 | - old_template_ref=read_template_ref_from_row(row), | ||
| 347 | - new_template_ref={}, | ||
| 348 | - skip_runtime_update=True, | ||
| 349 | - ) | ||
| 350 | - await gateway_request( | ||
| 351 | - normalized, | ||
| 352 | - "DELETE", | ||
| 353 | - f"{_SERVICE_POLICY_PATH}/{policy_id}", | ||
| 354 | - {}, | ||
| 355 | - ) | ||
| 356 | - return await self._handler.delete( | ||
| 357 | - _SERVICE_POLICY_TABLE, _service_policy_pk(normalized, policy_id) | ||
| 358 | - ) | ||
Mapplications/manager/manager_server/src/manager_server/core/instance/instance_data_lifecycle.py+4-37
| @@ -11,18 +11,6 @@ from manager_server.core.application_config.log_masking_rule import ( | |||
| 11 | push_log_masking_rules_sync_to_gateway, | 11 | push_log_masking_rules_sync_to_gateway, |
| 12 | seed_builtin_log_masking_rules, | 12 | seed_builtin_log_masking_rules, |
| 13 | ) | 13 | ) |
| 14 | -from manager_server.core.config_effective_policy.config_default_template_mapping import ( | ||
| 15 | - push_template_mappings_sync_to_gateway, | ||
| 16 | -) | ||
| 17 | -from manager_server.core.config_effective_policy.config_effective_agent_policy import ( | ||
| 18 | - push_agent_policies_sync_to_gateway, | ||
| 19 | -) | ||
| 20 | -from manager_server.core.config_effective_policy.config_effective_global_policy import ( | ||
| 21 | - push_global_policies_sync_to_gateway, | ||
| 22 | -) | ||
| 23 | -from manager_server.core.config_effective_policy.config_effective_service_policy import ( | ||
| 24 | - push_service_policies_sync_to_gateway, | ||
| 25 | -) | ||
| 26 | from manager_server.core.instance.instance_service import ( | 14 | from manager_server.core.instance.instance_service import ( |
| 27 | _LOG_MASKING_SEEDED_KEY, | 15 | _LOG_MASKING_SEEDED_KEY, |
| 28 | get_instance_row, | 16 | get_instance_row, |
| @@ -37,12 +25,6 @@ from manager_server.core.template.push_template_to_gateway import ( | |||
| 37 | rebuild_jid_template_ref_for_gateway, | 25 | rebuild_jid_template_ref_for_gateway, |
| 38 | sync_referenced_templates_to_gateway, | 26 | sync_referenced_templates_to_gateway, |
| 39 | ) | 27 | ) |
| 40 | -from manager_server.models.config_effective_policy_models import ( | ||
| 41 | - CONFIG_DEFAULT_TEMPLATE_MAPPING_TABLE_DEF, | ||
| 42 | - CONFIG_EFFECTIVE_AGENT_POLICY_TABLE_DEF, | ||
| 43 | - CONFIG_EFFECTIVE_GLOBAL_POLICY_TABLE_DEF, | ||
| 44 | - CONFIG_EFFECTIVE_SERVICE_POLICY_TABLE_DEF, | ||
| 45 | -) | ||
| 46 | from manager_server.models.jid_template_ref_models import ( | 28 | from manager_server.models.jid_template_ref_models import ( |
| 47 | JID_TEMPLATE_REF_TABLE_DEF, | 29 | JID_TEMPLATE_REF_TABLE_DEF, |
| 48 | ) | 30 | ) |
| @@ -58,13 +40,6 @@ logger = logging.getLogger(__name__) | |||
| 58 | 40 | ||
| 59 | _LIST_ALL_CAP = 10_000 | 41 | _LIST_ALL_CAP = 10_000 |
| 60 | 42 | ||
| 61 | -_MANAGER_POLICY_TABLES = ( | ||
| 62 | - CONFIG_EFFECTIVE_GLOBAL_POLICY_TABLE_DEF.table_name, | ||
| 63 | - CONFIG_EFFECTIVE_SERVICE_POLICY_TABLE_DEF.table_name, | ||
| 64 | - CONFIG_EFFECTIVE_AGENT_POLICY_TABLE_DEF.table_name, | ||
| 65 | - CONFIG_DEFAULT_TEMPLATE_MAPPING_TABLE_DEF.table_name, | ||
| 66 | -) | ||
| 67 | - | ||
| 68 | _MANAGER_INSTANCE_TABLES = ( | 43 | _MANAGER_INSTANCE_TABLES = ( |
| 69 | LOG_MASKING_RULE_TABLE_DEF.table_name, | 44 | LOG_MASKING_RULE_TABLE_DEF.table_name, |
| 70 | LOGGING_CONFIG_TABLE_DEF.table_name, | 45 | LOGGING_CONFIG_TABLE_DEF.table_name, |
| @@ -99,12 +74,10 @@ async def sync_data_to_gateway_on_register( | |||
| 99 | (见 ``maybe_full_sync_gateway_on_online``)。 | 74 | (见 ``maybe_full_sync_gateway_on_online``)。 |
| 100 | 75 | ||
| 101 | 顺序说明: | 76 | 顺序说明: |
| 102 | - 1. 模板(策略/映射依赖) | 77 | + 1. 模板(Agent 资源依赖) |
| 103 | 2. Agent 资源 | 78 | 2. Agent 资源 |
| 104 | - 3. 全局 / Service / Agent 策略(Agent 依赖 Service) | 79 | + 3. 日志脱敏规则 |
| 105 | - 4. 默认模板映射 | 80 | + 4. 重建 Manager 侧 jid_template_ref 索引 |
| 106 | - 5. 日志脱敏规则 | ||
| 107 | - 6. 重建 Manager 侧 jid_template_ref 索引 | ||
| 108 | """ | 81 | """ |
| 109 | jid = str(jiuwenclaw_id or "").strip() | 82 | jid = str(jiuwenclaw_id or "").strip() |
| 110 | if not jid: | 83 | if not jid: |
| @@ -132,10 +105,6 @@ async def sync_data_to_gateway_on_register( | |||
| 132 | raise | 105 | raise |
| 133 | 106 | ||
| 134 | for name, push_fn, before_fn in ( | 107 | for name, push_fn, before_fn in ( |
| 135 | - ("global_policies", push_global_policies_sync_to_gateway, None), | ||
| 136 | - ("service_policies", push_service_policies_sync_to_gateway, None), | ||
| 137 | - ("agent_policies", push_agent_policies_sync_to_gateway, None), | ||
| 138 | - ("template_mappings", push_template_mappings_sync_to_gateway, None), | ||
| 139 | ( | 108 | ( |
| 140 | "log_masking_rule", | 109 | "log_masking_rule", |
| 141 | push_log_masking_rules_sync_to_gateway, | 110 | push_log_masking_rules_sync_to_gateway, |
| @@ -177,8 +146,6 @@ def _delete_pk_for_row(table: str, row: Any, jiuwenclaw_id: str) -> dict[str, An | |||
| 177 | "slot": getattr(row, "slot"), | 146 | "slot": getattr(row, "slot"), |
| 178 | "template_id": getattr(row, "template_id"), | 147 | "template_id": getattr(row, "template_id"), |
| 179 | } | 148 | } |
| 180 | - if table in _MANAGER_POLICY_TABLES: | ||
| 181 | - return {"id": getattr(row, "id"), "jiuwenclaw_id": jiuwenclaw_id} | ||
| 182 | return {"id": getattr(row, "id")} | 149 | return {"id": getattr(row, "id")} |
| 183 | 150 | ||
| 184 | 151 | ||
| @@ -211,7 +178,7 @@ async def purge_manager_instance_data( | |||
| 211 | return {} | 178 | return {} |
| 212 | 179 | ||
| 213 | deleted_counts: dict[str, int] = {} | 180 | deleted_counts: dict[str, int] = {} |
| 214 | - for table in (*_MANAGER_POLICY_TABLES, *_MANAGER_INSTANCE_TABLES): | 181 | + for table in _MANAGER_INSTANCE_TABLES: |
| 215 | count = await _purge_table_rows(handler, table, jid) | 182 | count = await _purge_table_rows(handler, table, jid) |
| 216 | if count: | 183 | if count: |
| 217 | deleted_counts[table] = count | 184 | deleted_counts[table] = count |
Dapplications/manager/manager_server/src/manager_server/models/config_effective_policy_models.py+0-123
| @@ -1,123 +0,0 @@ | |||
| 1 | -"""企业级配置策略表定义(复合主键 id + jiuwenclaw_id,与 Gateway 返回的 id 对齐)。""" | ||
| 2 | - | ||
| 3 | -from __future__ import annotations | ||
| 4 | - | ||
| 5 | -from openjiuwen_runtime.foundation.db.table_def import ( | ||
| 6 | - ColumnDefinition, | ||
| 7 | - IndexDefinition, | ||
| 8 | - TableDefinition, | ||
| 9 | -) | ||
| 10 | - | ||
| 11 | -CONFIG_EFFECTIVE_GLOBAL_POLICY_TABLE_DEF = TableDefinition( | ||
| 12 | - table_name="config_effective_global_policy", | ||
| 13 | - columns=[ | ||
| 14 | - ColumnDefinition( | ||
| 15 | - "id", | ||
| 16 | - "integer", | ||
| 17 | - primary_key=True, | ||
| 18 | - autoincrement=False, | ||
| 19 | - nullable=False, | ||
| 20 | - ), | ||
| 21 | - ColumnDefinition("jiuwenclaw_id", "string", length=64, primary_key=True, nullable=False), | ||
| 22 | - ColumnDefinition("policy_id", "string", length=100, nullable=False), | ||
| 23 | - ColumnDefinition("policy_name", "string", length=128, nullable=False), | ||
| 24 | - ColumnDefinition("policy_desc", "string", length=512, nullable=True), | ||
| 25 | - ColumnDefinition("priority", "integer", nullable=False), | ||
| 26 | - ColumnDefinition("template_ref", "json", nullable=False), | ||
| 27 | - ColumnDefinition("enabled", "boolean", nullable=False, default=True), | ||
| 28 | - ColumnDefinition("data", "json", nullable=True), | ||
| 29 | - ColumnDefinition("created_at", "datetime", nullable=False), | ||
| 30 | - ColumnDefinition("updated_at", "datetime", nullable=False), | ||
| 31 | - ], | ||
| 32 | - indexes=[ | ||
| 33 | - IndexDefinition(["jiuwenclaw_id", "policy_id"], unique=True), | ||
| 34 | - ], | ||
| 35 | -) | ||
| 36 | - | ||
| 37 | -CONFIG_EFFECTIVE_SERVICE_POLICY_TABLE_DEF = TableDefinition( | ||
| 38 | - table_name="config_effective_service_policy", | ||
| 39 | - columns=[ | ||
| 40 | - ColumnDefinition( | ||
| 41 | - "id", | ||
| 42 | - "integer", | ||
| 43 | - primary_key=True, | ||
| 44 | - autoincrement=False, | ||
| 45 | - nullable=False, | ||
| 46 | - ), | ||
| 47 | - ColumnDefinition("jiuwenclaw_id", "string", length=64, primary_key=True, nullable=False), | ||
| 48 | - ColumnDefinition("policy_id", "string", length=100, nullable=False), | ||
| 49 | - ColumnDefinition("policy_name", "string", length=128, nullable=False), | ||
| 50 | - ColumnDefinition("policy_desc", "string", length=512, nullable=True), | ||
| 51 | - ColumnDefinition("service_id", "string", length=512, nullable=False), | ||
| 52 | - ColumnDefinition("priority", "integer", nullable=False), | ||
| 53 | - ColumnDefinition("match_expr", "string", length=8192, nullable=True), | ||
| 54 | - ColumnDefinition("template_ref", "json", nullable=False), | ||
| 55 | - ColumnDefinition("enabled", "boolean", nullable=False, default=True), | ||
| 56 | - ColumnDefinition("data", "json", nullable=True), | ||
| 57 | - ColumnDefinition("created_at", "datetime", nullable=False), | ||
| 58 | - ColumnDefinition("updated_at", "datetime", nullable=False), | ||
| 59 | - ], | ||
| 60 | - indexes=[ | ||
| 61 | - IndexDefinition(["jiuwenclaw_id", "policy_id"], unique=True), | ||
| 62 | - ], | ||
| 63 | -) | ||
| 64 | - | ||
| 65 | -CONFIG_EFFECTIVE_AGENT_POLICY_TABLE_DEF = TableDefinition( | ||
| 66 | - table_name="config_effective_agent_policy", | ||
| 67 | - columns=[ | ||
| 68 | - ColumnDefinition( | ||
| 69 | - "id", | ||
| 70 | - "integer", | ||
| 71 | - primary_key=True, | ||
| 72 | - autoincrement=False, | ||
| 73 | - nullable=False, | ||
| 74 | - ), | ||
| 75 | - ColumnDefinition("jiuwenclaw_id", "string", length=64, primary_key=True, nullable=False), | ||
| 76 | - ColumnDefinition("policy_id", "string", length=100, nullable=False), | ||
| 77 | - ColumnDefinition("policy_name", "string", length=128, nullable=False), | ||
| 78 | - ColumnDefinition("policy_desc", "string", length=512, nullable=True), | ||
| 79 | - ColumnDefinition("agent_id", "string", length=512, nullable=False), | ||
| 80 | - ColumnDefinition("workspace_dir", "string", length=512, nullable=True), | ||
| 81 | - ColumnDefinition("service_policy_id", "string", length=100, nullable=False), | ||
| 82 | - ColumnDefinition("priority", "integer", nullable=False, default=0), | ||
| 83 | - ColumnDefinition("match_expr", "string", length=8192, nullable=True), | ||
| 84 | - ColumnDefinition("template_ref", "json", nullable=False), | ||
| 85 | - ColumnDefinition("send_file_allowed", "boolean", nullable=True, default=True), | ||
| 86 | - ColumnDefinition("enabled", "boolean", nullable=False, default=True), | ||
| 87 | - ColumnDefinition("data", "json", nullable=True), | ||
| 88 | - ColumnDefinition("created_at", "datetime", nullable=False), | ||
| 89 | - ColumnDefinition("updated_at", "datetime", nullable=False), | ||
| 90 | - ], | ||
| 91 | - indexes=[ | ||
| 92 | - IndexDefinition(["jiuwenclaw_id", "policy_id"], unique=True), | ||
| 93 | - ], | ||
| 94 | -) | ||
| 95 | - | ||
| 96 | -CONFIG_DEFAULT_TEMPLATE_MAPPING_TABLE_DEF = TableDefinition( | ||
| 97 | - table_name="config_default_template_mapping", | ||
| 98 | - columns=[ | ||
| 99 | - ColumnDefinition( | ||
| 100 | - "id", | ||
| 101 | - "integer", | ||
| 102 | - primary_key=True, | ||
| 103 | - autoincrement=False, | ||
| 104 | - nullable=False, | ||
| 105 | - ), | ||
| 106 | - ColumnDefinition("jiuwenclaw_id", "string", length=64, primary_key=True, nullable=False), | ||
| 107 | - ColumnDefinition("policy_id", "string", length=100, nullable=False), | ||
| 108 | - ColumnDefinition("policy_name", "string", length=128, nullable=False), | ||
| 109 | - ColumnDefinition("policy_desc", "string", length=512, nullable=True), | ||
| 110 | - ColumnDefinition("scope_type", "string", length=32, nullable=False), | ||
| 111 | - ColumnDefinition("scope_id", "string", length=512, nullable=False), | ||
| 112 | - ColumnDefinition("priority", "integer", nullable=False), | ||
| 113 | - ColumnDefinition("template_id", "string", length=100, nullable=False), | ||
| 114 | - ColumnDefinition("template_type", "string", length=512, nullable=False), | ||
| 115 | - ColumnDefinition("enabled", "boolean", nullable=False, default=True), | ||
| 116 | - ColumnDefinition("data", "json", nullable=True), | ||
| 117 | - ColumnDefinition("created_at", "datetime", nullable=False), | ||
| 118 | - ColumnDefinition("updated_at", "datetime", nullable=False), | ||
| 119 | - ], | ||
| 120 | - indexes=[ | ||
| 121 | - IndexDefinition(["jiuwenclaw_id", "policy_id"], unique=True), | ||
| 122 | - ], | ||
| 123 | -) | ||
| @@ -4,12 +4,6 @@ from __future__ import annotations | |||
| 4 | 4 | ||
| 5 | from openjiuwen_runtime.foundation.db.handler import DBHandler | 5 | from openjiuwen_runtime.foundation.db.handler import DBHandler |
| 6 | 6 | ||
| 7 | -from manager_server.models.config_effective_policy_models import ( | ||
| 8 | - CONFIG_DEFAULT_TEMPLATE_MAPPING_TABLE_DEF, | ||
| 9 | - CONFIG_EFFECTIVE_AGENT_POLICY_TABLE_DEF, | ||
| 10 | - CONFIG_EFFECTIVE_GLOBAL_POLICY_TABLE_DEF, | ||
| 11 | - CONFIG_EFFECTIVE_SERVICE_POLICY_TABLE_DEF, | ||
| 12 | -) | ||
| 13 | from manager_server.models.instance_models import INSTANCE_INFO_TABLE_DEF | 7 | from manager_server.models.instance_models import INSTANCE_INFO_TABLE_DEF |
| 14 | from manager_server.models.key_models import ( | 8 | from manager_server.models.key_models import ( |
| 15 | INSTANCE_ENC_PUBKEY_TABLE_DEF, | 9 | INSTANCE_ENC_PUBKEY_TABLE_DEF, |
| @@ -51,10 +45,6 @@ ALL_TABLE_DEFINITIONS = ( | |||
| 51 | SKILL_WHITELIST_TEMPLATE_TABLE_DEF, | 45 | SKILL_WHITELIST_TEMPLATE_TABLE_DEF, |
| 52 | SERVICE_CONFIG_TEMPLATE_TABLE_DEF, | 46 | SERVICE_CONFIG_TEMPLATE_TABLE_DEF, |
| 53 | AGENT_TEMPLATE_TABLE_DEF, | 47 | AGENT_TEMPLATE_TABLE_DEF, |
| 54 | - CONFIG_DEFAULT_TEMPLATE_MAPPING_TABLE_DEF, | ||
| 55 | - CONFIG_EFFECTIVE_GLOBAL_POLICY_TABLE_DEF, | ||
| 56 | - CONFIG_EFFECTIVE_SERVICE_POLICY_TABLE_DEF, | ||
| 57 | - CONFIG_EFFECTIVE_AGENT_POLICY_TABLE_DEF, | ||
| 58 | JID_TEMPLATE_REF_TABLE_DEF, | 48 | JID_TEMPLATE_REF_TABLE_DEF, |
| 59 | *INSTANCE_ACCESS_TABLE_DEFINITIONS, | 49 | *INSTANCE_ACCESS_TABLE_DEFINITIONS, |
| 60 | *INSTANCE_RESOURCE_TABLE_DEFINITIONS, | 50 | *INSTANCE_RESOURCE_TABLE_DEFINITIONS, |
Dapplications/manager/manager_server/src/manager_server/routers/config_effective_policy_routers.py+0-395
| @@ -1,395 +0,0 @@ | |||
| 1 | -"""配置生效策略与默认模板映射 CRUD API(按组网实例路径隔离)。""" | ||
| 2 | - | ||
| 3 | -from __future__ import annotations | ||
| 4 | - | ||
| 5 | -from typing import Annotated | ||
| 6 | - | ||
| 7 | -from fastapi import APIRouter, Depends, HTTPException, Query | ||
| 8 | -from openjiuwen_runtime.foundation.db.handler import DBHandler | ||
| 9 | - | ||
| 10 | -from manager_server.infrastructure.db import get_db_handler | ||
| 11 | -from manager_server.schemas.common_schemas import ResponseModel | ||
| 12 | -from manager_server.schemas.config_effective_policy_schemas import ( | ||
| 13 | - ConfigDefaultTemplateMappingCreateBody, | ||
| 14 | - ConfigDefaultTemplateMappingListQuery, | ||
| 15 | - ConfigDefaultTemplateMappingUpdateBody, | ||
| 16 | - ConfigEffectiveAgentPolicyCreateBody, | ||
| 17 | - ConfigEffectiveAgentPolicyListQuery, | ||
| 18 | - ConfigEffectiveAgentPolicyUpdateBody, | ||
| 19 | - ConfigEffectiveGlobalPolicyCreateBody, | ||
| 20 | - ConfigEffectiveGlobalPolicyListQuery, | ||
| 21 | - ConfigEffectiveGlobalPolicyUpdateBody, | ||
| 22 | - ConfigEffectiveServicePolicyCreateBody, | ||
| 23 | - ConfigEffectiveServicePolicyListQuery, | ||
| 24 | - ConfigEffectiveServicePolicyUpdateBody, | ||
| 25 | -) | ||
| 26 | -from manager_server.core.config_effective_policy import ( | ||
| 27 | - ConfigDefaultTemplateMappingService, | ||
| 28 | - ConfigEffectiveAgentPolicyService, | ||
| 29 | - ConfigEffectiveGlobalPolicyService, | ||
| 30 | - ConfigEffectiveServicePolicyService, | ||
| 31 | -) | ||
| 32 | - | ||
| 33 | -# --- 默认模板映射 --- | ||
| 34 | - | ||
| 35 | -mapping_router = APIRouter( | ||
| 36 | - prefix="/{jiuwenclaw_id}/config-default-template-mappings", | ||
| 37 | -) | ||
| 38 | - | ||
| 39 | - | ||
| 40 | -def _mapping_svc(handler: DBHandler) -> ConfigDefaultTemplateMappingService: | ||
| 41 | - return ConfigDefaultTemplateMappingService(handler) | ||
| 42 | - | ||
| 43 | - | ||
| 44 | -@mapping_router.post("/", response_model=ResponseModel) | ||
| 45 | -async def create_template_mapping( | ||
| 46 | - jiuwenclaw_id: str, | ||
| 47 | - body: ConfigDefaultTemplateMappingCreateBody, | ||
| 48 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 49 | -): | ||
| 50 | - svc = _mapping_svc(handler) | ||
| 51 | - try: | ||
| 52 | - data = await svc.create(jiuwenclaw_id, body) | ||
| 53 | - except ValueError as exc: | ||
| 54 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 55 | - return ResponseModel(code=200, message="success", data=data.model_dump()) | ||
| 56 | - | ||
| 57 | - | ||
| 58 | -@mapping_router.get("/", response_model=ResponseModel) | ||
| 59 | -async def list_template_mappings( | ||
| 60 | - jiuwenclaw_id: str, | ||
| 61 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 62 | - query: Annotated[ConfigDefaultTemplateMappingListQuery, Query()], | ||
| 63 | -): | ||
| 64 | - svc = _mapping_svc(handler) | ||
| 65 | - try: | ||
| 66 | - data = await svc.list_mappings(jiuwenclaw_id, query) | ||
| 67 | - except ValueError as exc: | ||
| 68 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 69 | - return ResponseModel(code=200, message="success", data=data) | ||
| 70 | - | ||
| 71 | - | ||
| 72 | -@mapping_router.get("/{row_id}", response_model=ResponseModel) | ||
| 73 | -async def get_template_mapping( | ||
| 74 | - jiuwenclaw_id: str, | ||
| 75 | - row_id: int, | ||
| 76 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 77 | -): | ||
| 78 | - svc = _mapping_svc(handler) | ||
| 79 | - try: | ||
| 80 | - row = await svc.get(jiuwenclaw_id, row_id) | ||
| 81 | - except ValueError as exc: | ||
| 82 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 83 | - if row is None: | ||
| 84 | - raise HTTPException(status_code=404, detail="template mapping not found") | ||
| 85 | - return ResponseModel(code=200, message="success", data=row.model_dump()) | ||
| 86 | - | ||
| 87 | - | ||
| 88 | -@mapping_router.patch("/{row_id}", response_model=ResponseModel) | ||
| 89 | -async def update_template_mapping( | ||
| 90 | - jiuwenclaw_id: str, | ||
| 91 | - row_id: int, | ||
| 92 | - body: ConfigDefaultTemplateMappingUpdateBody, | ||
| 93 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 94 | -): | ||
| 95 | - svc = _mapping_svc(handler) | ||
| 96 | - try: | ||
| 97 | - row = await svc.update(jiuwenclaw_id, row_id, body) | ||
| 98 | - except ValueError as exc: | ||
| 99 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 100 | - if row is None: | ||
| 101 | - raise HTTPException(status_code=404, detail="template mapping not found") | ||
| 102 | - return ResponseModel(code=200, message="success", data=row.model_dump()) | ||
| 103 | - | ||
| 104 | - | ||
| 105 | -@mapping_router.delete("/{row_id}", response_model=ResponseModel) | ||
| 106 | -async def delete_template_mapping( | ||
| 107 | - jiuwenclaw_id: str, | ||
| 108 | - row_id: int, | ||
| 109 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 110 | -): | ||
| 111 | - svc = _mapping_svc(handler) | ||
| 112 | - try: | ||
| 113 | - ok = await svc.delete(jiuwenclaw_id, row_id) | ||
| 114 | - except ValueError as exc: | ||
| 115 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 116 | - if not ok: | ||
| 117 | - raise HTTPException(status_code=404, detail="template mapping not found") | ||
| 118 | - return ResponseModel( | ||
| 119 | - code=200, message="success", data={"deleted": True, "id": row_id} | ||
| 120 | - ) | ||
| 121 | - | ||
| 122 | -# --- Global 兜底 --- | ||
| 123 | - | ||
| 124 | -global_router = APIRouter( | ||
| 125 | - prefix="/{jiuwenclaw_id}/config-effective/global-policies", | ||
| 126 | -) | ||
| 127 | - | ||
| 128 | - | ||
| 129 | -def _global_svc(handler: DBHandler) -> ConfigEffectiveGlobalPolicyService: | ||
| 130 | - return ConfigEffectiveGlobalPolicyService(handler) | ||
| 131 | - | ||
| 132 | - | ||
| 133 | -@global_router.post("/", response_model=ResponseModel) | ||
| 134 | -async def create_global_policy( | ||
| 135 | - jiuwenclaw_id: str, | ||
| 136 | - body: ConfigEffectiveGlobalPolicyCreateBody, | ||
| 137 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 138 | -): | ||
| 139 | - svc = _global_svc(handler) | ||
| 140 | - try: | ||
| 141 | - data = await svc.create(jiuwenclaw_id, body) | ||
| 142 | - except ValueError as exc: | ||
| 143 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 144 | - return ResponseModel(code=200, message="success", data=data.model_dump()) | ||
| 145 | - | ||
| 146 | - | ||
| 147 | -@global_router.get("/", response_model=ResponseModel) | ||
| 148 | -async def list_global_policies( | ||
| 149 | - jiuwenclaw_id: str, | ||
| 150 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 151 | - query: Annotated[ConfigEffectiveGlobalPolicyListQuery, Query()], | ||
| 152 | -): | ||
| 153 | - svc = _global_svc(handler) | ||
| 154 | - try: | ||
| 155 | - data = await svc.list_policies(jiuwenclaw_id, query) | ||
| 156 | - except ValueError as exc: | ||
| 157 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 158 | - return ResponseModel(code=200, message="success", data=data) | ||
| 159 | - | ||
| 160 | - | ||
| 161 | -@global_router.get("/{row_id}", response_model=ResponseModel) | ||
| 162 | -async def get_global_policy( | ||
| 163 | - jiuwenclaw_id: str, | ||
| 164 | - row_id: int, | ||
| 165 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 166 | -): | ||
| 167 | - svc = _global_svc(handler) | ||
| 168 | - try: | ||
| 169 | - row = await svc.get(jiuwenclaw_id, row_id) | ||
| 170 | - except ValueError as exc: | ||
| 171 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 172 | - if row is None: | ||
| 173 | - raise HTTPException(status_code=404, detail="global policy not found") | ||
| 174 | - return ResponseModel(code=200, message="success", data=row.model_dump()) | ||
| 175 | - | ||
| 176 | - | ||
| 177 | -@global_router.patch("/{row_id}", response_model=ResponseModel) | ||
| 178 | -async def update_global_policy( | ||
| 179 | - jiuwenclaw_id: str, | ||
| 180 | - row_id: int, | ||
| 181 | - body: ConfigEffectiveGlobalPolicyUpdateBody, | ||
| 182 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 183 | -): | ||
| 184 | - svc = _global_svc(handler) | ||
| 185 | - try: | ||
| 186 | - row = await svc.update(jiuwenclaw_id, row_id, body) | ||
| 187 | - except ValueError as exc: | ||
| 188 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 189 | - if row is None: | ||
| 190 | - raise HTTPException(status_code=404, detail="global policy not found") | ||
| 191 | - return ResponseModel(code=200, message="success", data=row.model_dump()) | ||
| 192 | - | ||
| 193 | - | ||
| 194 | -@global_router.delete("/{row_id}", response_model=ResponseModel) | ||
| 195 | -async def delete_global_policy( | ||
| 196 | - jiuwenclaw_id: str, | ||
| 197 | - row_id: int, | ||
| 198 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 199 | -): | ||
| 200 | - svc = _global_svc(handler) | ||
| 201 | - try: | ||
| 202 | - ok = await svc.delete(jiuwenclaw_id, row_id) | ||
| 203 | - except ValueError as exc: | ||
| 204 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 205 | - if not ok: | ||
| 206 | - raise HTTPException(status_code=404, detail="global policy not found") | ||
| 207 | - return ResponseModel( | ||
| 208 | - code=200, message="success", data={"deleted": True, "id": row_id} | ||
| 209 | - ) | ||
| 210 | - | ||
| 211 | -# --- Service 层级 --- | ||
| 212 | - | ||
| 213 | -service_router = APIRouter( | ||
| 214 | - prefix="/{jiuwenclaw_id}/config-effective/service-policies", | ||
| 215 | -) | ||
| 216 | - | ||
| 217 | - | ||
| 218 | -def _service_svc(handler: DBHandler) -> ConfigEffectiveServicePolicyService: | ||
| 219 | - return ConfigEffectiveServicePolicyService(handler) | ||
| 220 | - | ||
| 221 | - | ||
| 222 | -@service_router.post("/", response_model=ResponseModel) | ||
| 223 | -async def create_service_policy( | ||
| 224 | - jiuwenclaw_id: str, | ||
| 225 | - body: ConfigEffectiveServicePolicyCreateBody, | ||
| 226 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 227 | -): | ||
| 228 | - svc = _service_svc(handler) | ||
| 229 | - try: | ||
| 230 | - data = await svc.create(jiuwenclaw_id, body) | ||
| 231 | - except ValueError as exc: | ||
| 232 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 233 | - return ResponseModel(code=200, message="success", data=data.model_dump()) | ||
| 234 | - | ||
| 235 | - | ||
| 236 | -@service_router.get("/", response_model=ResponseModel) | ||
| 237 | -async def list_service_policies( | ||
| 238 | - jiuwenclaw_id: str, | ||
| 239 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 240 | - query: Annotated[ConfigEffectiveServicePolicyListQuery, Query()], | ||
| 241 | -): | ||
| 242 | - svc = _service_svc(handler) | ||
| 243 | - try: | ||
| 244 | - data = await svc.list_policies(jiuwenclaw_id, query) | ||
| 245 | - except ValueError as exc: | ||
| 246 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 247 | - return ResponseModel(code=200, message="success", data=data) | ||
| 248 | - | ||
| 249 | - | ||
| 250 | -@service_router.get("/{row_id}", response_model=ResponseModel) | ||
| 251 | -async def get_service_policy( | ||
| 252 | - jiuwenclaw_id: str, | ||
| 253 | - row_id: int, | ||
| 254 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 255 | -): | ||
| 256 | - svc = _service_svc(handler) | ||
| 257 | - try: | ||
| 258 | - row = await svc.get(jiuwenclaw_id, row_id) | ||
| 259 | - except ValueError as exc: | ||
| 260 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 261 | - if row is None: | ||
| 262 | - raise HTTPException(status_code=404, detail="service policy not found") | ||
| 263 | - return ResponseModel(code=200, message="success", data=row.model_dump()) | ||
| 264 | - | ||
| 265 | - | ||
| 266 | -@service_router.patch("/{row_id}", response_model=ResponseModel) | ||
| 267 | -async def update_service_policy( | ||
| 268 | - jiuwenclaw_id: str, | ||
| 269 | - row_id: int, | ||
| 270 | - body: ConfigEffectiveServicePolicyUpdateBody, | ||
| 271 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 272 | -): | ||
| 273 | - svc = _service_svc(handler) | ||
| 274 | - try: | ||
| 275 | - row = await svc.update(jiuwenclaw_id, row_id, body) | ||
| 276 | - except ValueError as exc: | ||
| 277 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 278 | - if row is None: | ||
| 279 | - raise HTTPException(status_code=404, detail="service policy not found") | ||
| 280 | - return ResponseModel(code=200, message="success", data=row.model_dump()) | ||
| 281 | - | ||
| 282 | - | ||
| 283 | -@service_router.delete("/{row_id}", response_model=ResponseModel) | ||
| 284 | -async def delete_service_policy( | ||
| 285 | - jiuwenclaw_id: str, | ||
| 286 | - row_id: int, | ||
| 287 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 288 | -): | ||
| 289 | - svc = _service_svc(handler) | ||
| 290 | - try: | ||
| 291 | - ok = await svc.delete(jiuwenclaw_id, row_id) | ||
| 292 | - except ValueError as exc: | ||
| 293 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 294 | - if not ok: | ||
| 295 | - raise HTTPException(status_code=404, detail="service policy not found") | ||
| 296 | - return ResponseModel( | ||
| 297 | - code=200, message="success", data={"deleted": True, "id": row_id} | ||
| 298 | - ) | ||
| 299 | - | ||
| 300 | - | ||
| 301 | -# --- Agent 层级 --- | ||
| 302 | - | ||
| 303 | -agent_router = APIRouter( | ||
| 304 | - prefix="/{jiuwenclaw_id}/config-effective/agent-policies", | ||
| 305 | -) | ||
| 306 | - | ||
| 307 | - | ||
| 308 | -def _agent_svc(handler: DBHandler) -> ConfigEffectiveAgentPolicyService: | ||
| 309 | - return ConfigEffectiveAgentPolicyService(handler) | ||
| 310 | - | ||
| 311 | - | ||
| 312 | -@agent_router.post("/", response_model=ResponseModel) | ||
| 313 | -async def create_agent_policy( | ||
| 314 | - jiuwenclaw_id: str, | ||
| 315 | - body: ConfigEffectiveAgentPolicyCreateBody, | ||
| 316 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 317 | -): | ||
| 318 | - svc = _agent_svc(handler) | ||
| 319 | - try: | ||
| 320 | - data = await svc.create(jiuwenclaw_id, body) | ||
| 321 | - except ValueError as exc: | ||
| 322 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 323 | - return ResponseModel(code=200, message="success", data=data.model_dump()) | ||
| 324 | - | ||
| 325 | - | ||
| 326 | -@agent_router.get("/", response_model=ResponseModel) | ||
| 327 | -async def list_agent_policies( | ||
| 328 | - jiuwenclaw_id: str, | ||
| 329 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 330 | - query: Annotated[ConfigEffectiveAgentPolicyListQuery, Query()], | ||
| 331 | -): | ||
| 332 | - svc = _agent_svc(handler) | ||
| 333 | - try: | ||
| 334 | - data = await svc.list_policies(jiuwenclaw_id, query) | ||
| 335 | - except ValueError as exc: | ||
| 336 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 337 | - return ResponseModel(code=200, message="success", data=data) | ||
| 338 | - | ||
| 339 | - | ||
| 340 | -@agent_router.get("/{row_id}", response_model=ResponseModel) | ||
| 341 | -async def get_agent_policy( | ||
| 342 | - jiuwenclaw_id: str, | ||
| 343 | - row_id: int, | ||
| 344 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 345 | -): | ||
| 346 | - svc = _agent_svc(handler) | ||
| 347 | - try: | ||
| 348 | - row = await svc.get(jiuwenclaw_id, row_id) | ||
| 349 | - except ValueError as exc: | ||
| 350 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 351 | - if row is None: | ||
| 352 | - raise HTTPException(status_code=404, detail="agent policy not found") | ||
| 353 | - return ResponseModel(code=200, message="success", data=row.model_dump()) | ||
| 354 | - | ||
| 355 | - | ||
| 356 | -@agent_router.patch("/{row_id}", response_model=ResponseModel) | ||
| 357 | -async def update_agent_policy( | ||
| 358 | - jiuwenclaw_id: str, | ||
| 359 | - row_id: int, | ||
| 360 | - body: ConfigEffectiveAgentPolicyUpdateBody, | ||
| 361 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 362 | -): | ||
| 363 | - svc = _agent_svc(handler) | ||
| 364 | - try: | ||
| 365 | - row = await svc.update(jiuwenclaw_id, row_id, body) | ||
| 366 | - except ValueError as exc: | ||
| 367 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 368 | - if row is None: | ||
| 369 | - raise HTTPException(status_code=404, detail="agent policy not found") | ||
| 370 | - return ResponseModel(code=200, message="success", data=row.model_dump()) | ||
| 371 | - | ||
| 372 | - | ||
| 373 | -@agent_router.delete("/{row_id}", response_model=ResponseModel) | ||
| 374 | -async def delete_agent_policy( | ||
| 375 | - jiuwenclaw_id: str, | ||
| 376 | - row_id: int, | ||
| 377 | - handler: Annotated[DBHandler, Depends(get_db_handler)], | ||
| 378 | -): | ||
| 379 | - svc = _agent_svc(handler) | ||
| 380 | - try: | ||
| 381 | - ok = await svc.delete(jiuwenclaw_id, row_id) | ||
| 382 | - except ValueError as exc: | ||
| 383 | - raise HTTPException(status_code=400, detail=str(exc)) from exc | ||
| 384 | - if not ok: | ||
| 385 | - raise HTTPException(status_code=404, detail="agent policy not found") | ||
| 386 | - return ResponseModel( | ||
| 387 | - code=200, message="success", data={"deleted": True, "id": row_id} | ||
| 388 | - ) | ||
| 389 | - | ||
| 390 | - | ||
| 391 | -config_effective_policy_router = APIRouter() | ||
| 392 | -config_effective_policy_router.include_router(mapping_router) | ||
| 393 | -config_effective_policy_router.include_router(global_router) | ||
| 394 | -config_effective_policy_router.include_router(service_router) | ||
| 395 | -config_effective_policy_router.include_router(agent_router) | ||
| @@ -5,7 +5,6 @@ import os | |||
| 5 | from fastapi import APIRouter, FastAPI | 5 | from fastapi import APIRouter, FastAPI |
| 6 | 6 | ||
| 7 | from .application_config_routers import application_config_router | 7 | from .application_config_routers import application_config_router |
| 8 | -from .config_effective_policy_routers import config_effective_policy_router | ||
| 9 | from .user_console_routers import user_console_router | 8 | from .user_console_routers import user_console_router |
| 10 | from .instance_access_routers import gateway_lookup_router, instance_grant_router | 9 | from .instance_access_routers import gateway_lookup_router, instance_grant_router |
| 11 | from .instance_resource_routers import instance_resource_router | 10 | from .instance_resource_routers import instance_resource_router |
| @@ -33,11 +32,6 @@ def router_register(app: FastAPI) -> None: | |||
| 33 | prefix=INSTANCES_PREFIX, | 32 | prefix=INSTANCES_PREFIX, |
| 34 | tags=["Application Config"], | 33 | tags=["Application Config"], |
| 35 | ) | 34 | ) |
| 36 | - v1_router.include_router( | ||
| 37 | - config_effective_policy_router, | ||
| 38 | - prefix=INSTANCES_PREFIX, | ||
| 39 | - tags=["Config Effective Policy"], | ||
| 40 | - ) | ||
| 41 | 35 | ||
| 42 | # 实例准入:用户/组织 ↔ 实例授权(instance_grant)。 | 36 | # 实例准入:用户/组织 ↔ 实例授权(instance_grant)。 |
| 43 | v1_router.include_router( | 37 | v1_router.include_router( |
Dapplications/manager/manager_server/src/manager_server/schemas/config_effective_policy_schemas.py+0-301
| @@ -1,301 +0,0 @@ | |||
| 1 | -"""配置生效策略与默认模板映射 API 请求/响应模型。""" | ||
| 2 | - | ||
| 3 | -from __future__ import annotations | ||
| 4 | - | ||
| 5 | -from typing import Annotated, Any | ||
| 6 | - | ||
| 7 | -from pydantic import BaseModel, ConfigDict, Field, BeforeValidator | ||
| 8 | - | ||
| 9 | -from manager_server.infrastructure.common import ( | ||
| 10 | - coerce_routing_id, | ||
| 11 | - coerce_routing_id_optional, | ||
| 12 | -) | ||
| 13 | -from manager_server.infrastructure.template_ref import ( | ||
| 14 | - normalize_template_ref, | ||
| 15 | - normalize_template_ref_optional, | ||
| 16 | -) | ||
| 17 | -from manager_server.schemas.template_slot_schemas import ( | ||
| 18 | - DefaultTemplateMappingTypeLiteral, | ||
| 19 | - MappingScopeTypeLiteral, | ||
| 20 | - TEMPLATE_REF_SLOTS, | ||
| 21 | -) | ||
| 22 | - | ||
| 23 | -TemplateRefField = Annotated[dict[str, list[str]], BeforeValidator(normalize_template_ref)] | ||
| 24 | -OptionalTemplateRefField = Annotated[ | ||
| 25 | - dict[str, list[str]] | None, | ||
| 26 | - BeforeValidator(normalize_template_ref_optional), | ||
| 27 | -] | ||
| 28 | -RoutingIdField = Annotated[str, BeforeValidator(coerce_routing_id)] | ||
| 29 | -OptionalRoutingIdField = Annotated[str | None, BeforeValidator(coerce_routing_id_optional)] | ||
| 30 | - | ||
| 31 | - | ||
| 32 | -# --- config_effective_agent_policy --- | ||
| 33 | - | ||
| 34 | -class ConfigEffectiveAgentPolicyCreateBody(BaseModel): | ||
| 35 | - model_config = ConfigDict(str_strip_whitespace=True) | ||
| 36 | - | ||
| 37 | - policy_name: str = Field(..., max_length=128, min_length=1) | ||
| 38 | - policy_desc: str | None = Field(default=None, max_length=512) | ||
| 39 | - agent_id: RoutingIdField = Field(..., max_length=512) | ||
| 40 | - workspace_dir: OptionalRoutingIdField = Field( | ||
| 41 | - default=None, | ||
| 42 | - max_length=512, | ||
| 43 | - description=( | ||
| 44 | - "数据存储目录逻辑键;可写 ${user_id}/${group_id}/${bot_id} 或固定值;" | ||
| 45 | - "未配置时运行时默认 ${group_id}${bot_id}${user_id}" | ||
| 46 | - ), | ||
| 47 | - ) | ||
| 48 | - service_policy_id: str = Field(..., max_length=100, min_length=1) | ||
| 49 | - priority: int = Field(default=0) | ||
| 50 | - match_expr: str | None = None | ||
| 51 | - template_ref: TemplateRefField = Field( | ||
| 52 | - default_factory=dict, | ||
| 53 | - description="槽位名 -> template_id 数组;元素可为 UUID 或 ${user::…}/${group::…} or <template_id>", | ||
| 54 | - ) | ||
| 55 | - send_file_allowed: bool = True | ||
| 56 | - enabled: bool = True | ||
| 57 | - data: dict[str, Any] | None = None | ||
| 58 | - | ||
| 59 | - | ||
| 60 | -class ConfigEffectiveAgentPolicyUpdateBody(BaseModel): | ||
| 61 | - policy_name: str | None = Field(default=None, max_length=128, min_length=1) | ||
| 62 | - policy_desc: str | None = Field(default=None, max_length=512) | ||
| 63 | - agent_id: OptionalRoutingIdField = Field(default=None, max_length=512) | ||
| 64 | - workspace_dir: OptionalRoutingIdField = Field(default=None, max_length=512) | ||
| 65 | - service_policy_id: str | None = Field(default=None, max_length=100, min_length=1) | ||
| 66 | - priority: int | None = None | ||
| 67 | - match_expr: str | None = None | ||
| 68 | - template_ref: OptionalTemplateRefField = None | ||
| 69 | - send_file_allowed: bool | None = None | ||
| 70 | - enabled: bool | None = None | ||
| 71 | - data: dict[str, Any] | None = None | ||
| 72 | - | ||
| 73 | - | ||
| 74 | -class ConfigEffectiveAgentPolicyOut(BaseModel): | ||
| 75 | - id: int | ||
| 76 | - jiuwenclaw_id: str | ||
| 77 | - policy_id: str | ||
| 78 | - policy_name: str | ||
| 79 | - policy_desc: str | None | ||
| 80 | - agent_id: str | ||
| 81 | - workspace_dir: str | None = None | ||
| 82 | - service_policy_id: str | ||
| 83 | - priority: int | ||
| 84 | - match_expr: str | None | ||
| 85 | - template_ref: dict[str, list[str]] | ||
| 86 | - send_file_allowed: bool | ||
| 87 | - enabled: bool | ||
| 88 | - data: dict[str, Any] | None | ||
| 89 | - created_at: str | None | ||
| 90 | - updated_at: str | None | ||
| 91 | - | ||
| 92 | - | ||
| 93 | -class ConfigEffectiveAgentPolicyListQuery(BaseModel): | ||
| 94 | - """Agent 层级策略列表查询参数。""" | ||
| 95 | - | ||
| 96 | - page: int = Field(1, ge=1) | ||
| 97 | - page_size: int = Field(20, ge=1, le=200) | ||
| 98 | - service_policy_id: str | None = Field( | ||
| 99 | - default=None, | ||
| 100 | - description="按服务级策略 policy_id 筛选", | ||
| 101 | - ) | ||
| 102 | - enabled: bool | None = None | ||
| 103 | - send_file_allowed: bool | None = None | ||
| 104 | - search: str | None = Field( | ||
| 105 | - default=None, | ||
| 106 | - description="搜索策略 ID、名称、描述、Agent ID、workspace_dir、关联服务策略、优先级或匹配表达式", | ||
| 107 | - ) | ||
| 108 | - sort_by: str | None = Field( | ||
| 109 | - default=None, | ||
| 110 | - description=( | ||
| 111 | - "排序字段:policy_name、policy_desc、service_policy_id、priority、" | ||
| 112 | - "match_expr、agent_id、workspace_dir、updated_at" | ||
| 113 | - ), | ||
| 114 | - ) | ||
| 115 | - sort_order: str | None = Field(default=None, description="排序方向:asc、desc") | ||
| 116 | - | ||
| 117 | - | ||
| 118 | -# --- config_effective_service_policy --- | ||
| 119 | - | ||
| 120 | -class ConfigEffectiveServicePolicyCreateBody(BaseModel): | ||
| 121 | - model_config = ConfigDict(str_strip_whitespace=True) | ||
| 122 | - | ||
| 123 | - policy_name: str = Field(..., max_length=128, min_length=1) | ||
| 124 | - policy_desc: str | None = Field(default=None, max_length=512) | ||
| 125 | - service_id: RoutingIdField = Field(..., max_length=512) | ||
| 126 | - priority: int | ||
| 127 | - match_expr: str | None = None | ||
| 128 | - template_ref: TemplateRefField = Field(default_factory=dict) | ||
| 129 | - enabled: bool = True | ||
| 130 | - data: dict[str, Any] | None = None | ||
| 131 | - | ||
| 132 | - | ||
| 133 | -class ConfigEffectiveServicePolicyUpdateBody(BaseModel): | ||
| 134 | - policy_name: str | None = Field(default=None, max_length=128, min_length=1) | ||
| 135 | - policy_desc: str | None = Field(default=None, max_length=512) | ||
| 136 | - service_id: OptionalRoutingIdField = Field(default=None, max_length=512) | ||
| 137 | - priority: int | None = None | ||
| 138 | - match_expr: str | None = None | ||
| 139 | - template_ref: OptionalTemplateRefField = None | ||
| 140 | - enabled: bool | None = None | ||
| 141 | - data: dict[str, Any] | None = None | ||
| 142 | - | ||
| 143 | - | ||
| 144 | -class ConfigEffectiveServicePolicyOut(BaseModel): | ||
| 145 | - id: int | ||
| 146 | - jiuwenclaw_id: str | ||
| 147 | - policy_id: str | ||
| 148 | - policy_name: str | ||
| 149 | - policy_desc: str | None | ||
| 150 | - service_id: str | ||
| 151 | - priority: int | ||
| 152 | - match_expr: str | None | ||
| 153 | - template_ref: dict[str, list[str]] | ||
| 154 | - enabled: bool | ||
| 155 | - data: dict[str, Any] | None | ||
| 156 | - created_at: str | None | ||
| 157 | - updated_at: str | None | ||
| 158 | - | ||
| 159 | - | ||
| 160 | -class ConfigEffectiveServicePolicyListQuery(BaseModel): | ||
| 161 | - """服务级策略列表查询参数。""" | ||
| 162 | - | ||
| 163 | - page: int = Field(1, ge=1) | ||
| 164 | - page_size: int = Field(20, ge=1, le=200) | ||
| 165 | - enabled: bool | None = None | ||
| 166 | - search: str | None = Field( | ||
| 167 | - default=None, | ||
| 168 | - description="搜索策略 ID、名称、描述、服务 ID、优先级或匹配表达式", | ||
| 169 | - ) | ||
| 170 | - sort_by: str | None = Field( | ||
| 171 | - default=None, | ||
| 172 | - description="排序字段:policy_name、policy_desc、priority、match_expr、service_id、updated_at", | ||
| 173 | - ) | ||
| 174 | - sort_order: str | None = Field(default=None, description="排序方向:asc、desc") | ||
| 175 | - | ||
| 176 | - | ||
| 177 | -# --- config_effective_global_policy --- | ||
| 178 | - | ||
| 179 | -class ConfigEffectiveGlobalPolicyCreateBody(BaseModel): | ||
| 180 | - model_config = ConfigDict(str_strip_whitespace=True) | ||
| 181 | - | ||
| 182 | - policy_name: str = Field(..., max_length=128, min_length=1) | ||
| 183 | - policy_desc: str | None = Field(default=None, max_length=512) | ||
| 184 | - priority: int = 0 | ||
| 185 | - template_ref: TemplateRefField = Field(default_factory=dict) | ||
| 186 | - enabled: bool = True | ||
| 187 | - data: dict[str, Any] | None = None | ||
| 188 | - | ||
| 189 | - | ||
| 190 | -class ConfigEffectiveGlobalPolicyUpdateBody(BaseModel): | ||
| 191 | - policy_name: str | None = Field(default=None, max_length=128, min_length=1) | ||
| 192 | - policy_desc: str | None = Field(default=None, max_length=512) | ||
| 193 | - priority: int | None = None | ||
| 194 | - template_ref: OptionalTemplateRefField = None | ||
| 195 | - enabled: bool | None = None | ||
| 196 | - data: dict[str, Any] | None = None | ||
| 197 | - | ||
| 198 | - | ||
| 199 | -class ConfigEffectiveGlobalPolicyOut(BaseModel): | ||
| 200 | - id: int | ||
| 201 | - jiuwenclaw_id: str | ||
| 202 | - policy_id: str | ||
| 203 | - policy_name: str | ||
| 204 | - policy_desc: str | None | ||
| 205 | - priority: int | ||
| 206 | - template_ref: dict[str, list[str]] | ||
| 207 | - enabled: bool | ||
| 208 | - data: dict[str, Any] | None | ||
| 209 | - created_at: str | None | ||
| 210 | - updated_at: str | None | ||
| 211 | - | ||
| 212 | - | ||
| 213 | -class ConfigEffectiveGlobalPolicyListQuery(BaseModel): | ||
| 214 | - """全局兜底策略列表查询参数。""" | ||
| 215 | - | ||
| 216 | - page: int = Field(1, ge=1) | ||
| 217 | - page_size: int = Field(20, ge=1, le=200) | ||
| 218 | - enabled: bool | None = None | ||
| 219 | - search: str | None = Field(default=None, description="搜索策略 ID、名称、描述或优先级") | ||
| 220 | - sort_by: str | None = Field( | ||
| 221 | - default=None, | ||
| 222 | - description="排序字段:policy_name、policy_desc、priority、updated_at", | ||
| 223 | - ) | ||
| 224 | - sort_order: str | None = Field(default=None, description="排序方向:asc、desc") | ||
| 225 | - | ||
| 226 | - | ||
| 227 | -# --- config_default_template_mapping --- | ||
| 228 | - | ||
| 229 | - | ||
| 230 | -class ConfigDefaultTemplateMappingCreateBody(BaseModel): | ||
| 231 | - model_config = ConfigDict(str_strip_whitespace=True) | ||
| 232 | - | ||
| 233 | - policy_name: str = Field(..., max_length=128, min_length=1) | ||
| 234 | - policy_desc: str | None = Field(default=None, max_length=512) | ||
| 235 | - scope_type: MappingScopeTypeLiteral | ||
| 236 | - scope_id: str = Field(..., max_length=512, min_length=1) | ||
| 237 | - priority: int = 0 | ||
| 238 | - template_id: str = Field(..., max_length=100, min_length=1) | ||
| 239 | - template_type: DefaultTemplateMappingTypeLiteral | ||
| 240 | - enabled: bool = True | ||
| 241 | - data: dict[str, Any] | None = None | ||
| 242 | - | ||
| 243 | - | ||
| 244 | -class ConfigDefaultTemplateMappingUpdateBody(BaseModel): | ||
| 245 | - model_config = ConfigDict(str_strip_whitespace=True) | ||
| 246 | - | ||
| 247 | - policy_name: str | None = Field(default=None, max_length=128, min_length=1) | ||
| 248 | - policy_desc: str | None = Field(default=None, max_length=512) | ||
| 249 | - scope_type: MappingScopeTypeLiteral | None = None | ||
| 250 | - scope_id: str | None = Field(default=None, max_length=512, min_length=1) | ||
| 251 | - priority: int | None = None | ||
| 252 | - template_id: str | None = Field(default=None, max_length=100, min_length=1) | ||
| 253 | - template_type: DefaultTemplateMappingTypeLiteral | None = None | ||
| 254 | - enabled: bool | None = None | ||
| 255 | - data: dict[str, Any] | None = None | ||
| 256 | - | ||
| 257 | - | ||
| 258 | -class ConfigDefaultTemplateMappingOut(BaseModel): | ||
| 259 | - id: int | ||
| 260 | - jiuwenclaw_id: str | ||
| 261 | - policy_id: str | ||
| 262 | - policy_name: str | ||
| 263 | - policy_desc: str | None | ||
| 264 | - scope_type: MappingScopeTypeLiteral | ||
| 265 | - scope_id: str | ||
| 266 | - priority: int | ||
| 267 | - template_id: str | ||
| 268 | - template_type: DefaultTemplateMappingTypeLiteral | ||
| 269 | - enabled: bool | ||
| 270 | - data: dict[str, Any] | None | ||
| 271 | - created_at: str | None | ||
| 272 | - updated_at: str | None | ||
| 273 | - | ||
| 274 | - | ||
| 275 | -class ConfigDefaultTemplateMappingListQuery(BaseModel): | ||
| 276 | - """默认模板映射列表查询参数。""" | ||
| 277 | - | ||
| 278 | - page: int = Field(1, ge=1) | ||
| 279 | - page_size: int = Field(20, ge=1, le=200) | ||
| 280 | - scope_type: MappingScopeTypeLiteral | None = Field( | ||
| 281 | - default=None, description="按 scope_type 精确筛选" | ||
| 282 | - ) | ||
| 283 | - scope_id: str | None = Field(default=None, description="按 scope_id 精确筛选") | ||
| 284 | - template_type: DefaultTemplateMappingTypeLiteral | None = Field( | ||
| 285 | - default=None, | ||
| 286 | - description=f"模板类型:{' / '.join(sorted(TEMPLATE_REF_SLOTS))}", | ||
| 287 | - ) | ||
| 288 | - template_id: str | None = Field(default=None, description="按 template_id 精确筛选") | ||
| 289 | - enabled: bool | None = None | ||
| 290 | - search: str | None = Field( | ||
| 291 | - default=None, | ||
| 292 | - description="搜索策略 ID、名称、描述、作用域类型、作用域 ID、槽位、模板 ID 或优先级", | ||
| 293 | - ) | ||
| 294 | - sort_by: str | None = Field( | ||
| 295 | - default=None, | ||
| 296 | - description=( | ||
| 297 | - "排序字段:policy_name、policy_desc、priority、scope_type、scope_id、" | ||
| 298 | - "template_type、template_id、updated_at" | ||
| 299 | - ), | ||
| 300 | - ) | ||
| 301 | - sort_order: str | None = Field(default=None, description="排序方向:asc、desc") | ||
| @@ -81,9 +81,9 @@ def test_has_sensitive(): | |||
| 81 | 81 | ||
| 82 | 82 | ||
| 83 | def test_unregistered_section_passthrough(): | 83 | def test_unregistered_section_passthrough(): |
| 84 | - body = {"op": "create", "mapping": {"scope_type": "user", "scope_id": "u1"}} | 84 | + body = {"op": "create", "template": {"name": "x"}} |
| 85 | enc_body, fields = encrypt_sensitive_fields( | 85 | enc_body, fields = encrypt_sensitive_fields( |
| 86 | - "config_default_template_mappings", body, new_dek() | 86 | + "unknown_section_name", body, new_dek() |
| 87 | ) | 87 | ) |
| 88 | assert fields == [] | 88 | assert fields == [] |
| 89 | assert enc_body == body | 89 | assert enc_body == body |