{
  "openapi": "3.0.3",
  "info": {
    "title": "Cloud WAF API",
    "version": "v1",
    "description": "企业 Web 防护管理平台 REST API。统一返回 JSON 信封 {code, message, data}；受保护接口支持 Bearer 会话 Token 与 ApiKey 机器调用凭证双通道认证。\n\n**API Key scope 重要约定**：scope 列表使用**精确字符串相等**匹配（大小写敏感），**不展开通配符**——写 `guard.*` / `guard.domain.*` 进 scope 不会命中任何接口；如需 guard 全模块权限请逐条列举叶子 perm key。scope 与用户角色 RBAC 是独立判定层（effective_perms = user.RBAC ∩ key.scope），用户 RBAC 通配符可用、key scope 不可用。失败错码：1053 (scope_insufficient) / 1056 (apikey forbidden, 跨 OEM)。\n\n本 spec 为 minimal 版本，覆盖代表性公开端点与高频鉴权端点；完整接口说明请参考 /docs/api。",
    "contact": { "name": "Cloud WAF", "url": "/docs/api" },
    "license": { "name": "Proprietary" }
  },
  "servers": [
    { "url": "/", "description": "current host" }
  ],
  "tags": [
    { "name": "cli", "description": "CLI 二进制分发（公开）" },
    { "name": "auth", "description": "登录登出" },
    { "name": "sys.user", "description": "用户管理" },
    { "name": "sys.apikey", "description": "API Key 管理（机器调用凭证，与 Bearer session 并行）" },
    { "name": "guard.domain", "description": "域名接入" },
    { "name": "analytics", "description": "统计分析与图表查询" },
    { "name": "plan", "description": "套餐目录查询（只读）。创建/编辑/删除套餐、为用户开通、订阅查询属平台控制台管理操作，不在对外 API 范围。" }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "session-token"
      },
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "API Key 通道，请求头格式 `Authorization: ApiKey zck_<prefix>.<secret>`。与 Bearer session 并行；effective_perms = user.RBAC ∩ key.scope（scope 只能收窄）。**scope 精确字符串匹配，不展开通配符**——`guard.*` 不会命中任何接口；签发时请逐条列举叶子 perm key。失败错码：1053 (scope_insufficient) / 1056 (apikey forbidden, 跨 OEM)。"
      }
    },
    "schemas": {
      "PlanVO": {
        "type": "object",
        "description": "套餐详情响应（只读字段，对齐 service.PlanVO）",
        "properties": {
          "plan_id":     { "type": "string", "description": "套餐唯一 ID（UUID）" },
          "name":        { "type": "string", "description": "套餐名称" },
          "content":     { "type": "object", "description": "套餐配额 JSON（各产品类型对应字段不同）" },
          "price":       { "type": "number", "format": "float", "description": "套餐价格（保留 2 位小数）" },
          "scene":       { "type": "string", "description": "适用场景描述" },
          "comment":     { "type": "string", "description": "备注" },
          "open_status": { "type": "boolean", "description": "是否公开售卖" },
          "valid":       { "type": "integer", "format": "int64", "description": "有效期（天数）" },
          "effect":      { "type": "integer", "format": "int32", "description": "生效方式" },
          "level":       { "type": "integer", "format": "int64", "description": "套餐等级" },
          "creator_id":  { "type": "string", "description": "创建者 ID" },
          "ctime":       { "type": "integer", "format": "int64", "description": "创建时间（Unix 毫秒）" },
          "utime":       { "type": "integer", "format": "int64", "description": "更新时间（Unix 毫秒）" },
          "version":     { "type": "string", "description": "套餐来源版本（cloud / zmod）" },
          "prod_type":   { "type": "integer", "format": "int32", "description": "产品类型（2=WAF 4=Monitor 32=GFIP）" }
        }
      },
      "BatchChartResult": {
        "type": "object",
        "description": "Chart 接口统一响应形状（docs/specs/chart-contract.md）。这是新系统唯一对外图表契约；每个 chart-key 响应只输出这 5 字段，禁止 series/totals/kpis/points/list 等历史字段作为顶层字段。",
        "required": ["chart_key", "render_hint", "schema", "rows"],
        "properties": {
          "chart_key": {
            "type": "string",
            "example": "access/status",
            "description": "与请求 charts[].key 完全一致"
          },
          "render_hint": {
            "type": "string",
            "enum": [
              "kpi",
              "categorical_distribution",
              "categorical_distribution_over_time",
              "time_series_single",
              "time_series_multi",
              "topn",
              "geo",
              "table"
            ],
            "description": "前端渲染分发依据，仅 8 词汇白名单。新增需双方评审"
          },
          "schema": { "$ref": "#/components/schemas/ChartSchema" },
          "rows": {
            "type": "array",
            "description": "tidy 长表（一行一个观测）。空数据必须返回 []，禁止 null。每行 key 严格落在 schema.dimensions[].name + measures[].name 内。",
            "items": { "type": "object", "additionalProperties": true }
          },
          "meta": {
            "type": "object",
            "description": "调试 / 性能字段（source / cache / latency_ms / partial / available / row_extras 等），前端可显示也可忽略。",
            "additionalProperties": true
          }
        }
      },
      "ChartSchema": {
        "type": "object",
        "description": "rows 列元信息，前端据此自动选 axis/formatter/unit/配色。",
        "required": ["dimensions", "measures"],
        "properties": {
          "dimensions": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ChartDimension" }
          },
          "measures": {
            "type": "array",
            "minItems": 1,
            "items": { "$ref": "#/components/schemas/ChartMeasure" }
          }
        }
      },
      "ChartDimension": {
        "type": "object",
        "required": ["name", "type"],
        "properties": {
          "name": { "type": "string", "description": "列名，与 rows 里的 key 严格对应" },
          "type": {
            "type": "string",
            "enum": ["enum", "string", "timestamp", "geo"],
            "description": "数据类型"
          },
          "unit": {
            "type": "string",
            "description": "type=timestamp 时必填（ms / s）"
          },
          "values": {
            "type": "array",
            "items": { "type": "string" },
            "description": "type=enum 时必填，前端按此顺序与配色固定"
          }
        }
      },
      "ChartMeasure": {
        "type": "object",
        "required": ["name", "type"],
        "properties": {
          "name": { "type": "string" },
          "type": {
            "type": "string",
            "enum": ["integer", "float", "percent"]
          },
          "unit": {
            "type": "string",
            "description": "显示单位：requests / bytes / ms / % / qps / events / bps 等"
          },
          "format": {
            "type": "string",
            "enum": ["iec", "short"],
            "description": "前端格式化提示"
          }
        }
      },
      "Envelope": {
        "type": "object",
        "required": ["code", "message"],
        "properties": {
          "code": { "type": "integer", "example": 0, "description": "0 = 成功，非 0 = 业务错误码" },
          "message": { "type": "string", "example": "success" },
          "data": { "description": "业务负载，类型随接口不同" }
        }
      },
      "CLIVersion": {
        "type": "object",
        "properties": {
          "version": { "type": "string", "example": "v0.1.0-31" },
          "git_hash": { "type": "string", "example": "f635a39" },
          "build_time": { "type": "string", "example": "2026-04-28T10:00:00Z" },
          "platforms": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "os": { "type": "string", "enum": ["linux", "darwin", "windows"] },
                "arch": { "type": "string", "enum": ["amd64", "arm64"] },
                "download_url": { "type": "string" },
                "sha256": { "type": "string" }
              }
            }
          }
        }
      },
      "LoginRequest": {
        "type": "object",
        "required": ["username", "password"],
        "properties": {
          "username": { "type": "string", "example": "admin" },
          "password": { "type": "string", "format": "password" }
        }
      },
      "LoginResponse": {
        "type": "object",
        "properties": {
          "token": { "type": "string", "description": "Bearer token，放入 Authorization 请求头" },
          "user_id": { "type": "integer" },
          "expires_at": { "type": "string", "format": "date-time" }
        }
      },
      "User": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "username": { "type": "string" },
          "email": { "type": "string", "format": "email" },
          "role_id": { "type": "integer" },
          "oem_id": { "type": "integer" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "Domain": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "domain": { "type": "string", "example": "example.com" },
          "protected": { "type": "boolean" },
          "cert_id": { "type": "integer", "nullable": true },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "AnalyticsQuery": {
        "type": "object",
        "properties": {
          "window": { "type": "string", "example": "last_24h" },
          "stime": { "type": "integer", "format": "int64", "description": "Unix milliseconds" },
          "etime": { "type": "integer", "format": "int64", "description": "Unix milliseconds" },
          "site_id": { "type": "string" },
          "domain_id": { "type": "string" },
          "target_user_id": { "type": "string" },
          "compare": { "type": "boolean" },
          "top": { "type": "integer", "default": 10, "maximum": 100 },
          "order": { "type": "string" },
          "page": { "type": "integer", "default": 1 },
          "size": { "type": "integer", "default": 20, "maximum": 100 }
        }
      },
      "AnalyticsChartReq": {
        "type": "object",
        "required": ["key"],
        "properties": {
          "key": { "type": "string", "example": "kpi" },
          "params": { "type": "object", "additionalProperties": true }
        }
      },
      "AnalyticsBatchReq": {
        "type": "object",
        "required": ["page", "time_window", "charts"],
        "properties": {
          "page": { "type": "string", "enum": ["overview", "access", "protect", "ai", "bot", "alert", "health", "ops", "closure", "cache"] },
          "time_window": { "type": "string", "example": "last_24h" },
          "stime": { "type": "integer", "format": "int64" },
          "etime": { "type": "integer", "format": "int64" },
          "site_id": { "type": "string" },
          "domain_id": { "type": "string" },
          "target_user_id": { "type": "string" },
          "compare": { "type": "boolean" },
          "charts": { "type": "array", "items": { "$ref": "#/components/schemas/AnalyticsChartReq" }, "minItems": 1, "maxItems": 20 }
        }
      },
      "AnalyticsExportReq": {
        "allOf": [
          { "$ref": "#/components/schemas/AnalyticsQuery" },
          {
            "type": "object",
            "required": ["charts"],
            "properties": {
              "format": { "type": "string", "enum": ["csv", "json"], "default": "csv" },
              "charts": { "type": "array", "items": { "$ref": "#/components/schemas/AnalyticsChartReq" }, "minItems": 1, "maxItems": 20 }
            }
          }
        ]
      },
      "AnalyticsWindow": {
        "type": "object",
        "properties": {
          "stime": { "type": "integer", "format": "int64" },
          "etime": { "type": "integer", "format": "int64" },
          "granularity": { "type": "string", "example": "5m" },
          "bucket_table": { "type": "string" }
        }
      },
      "CreateAPIKeyRequest": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "name": { "type": "string", "maxLength": 100, "example": "生产对接" },
          "scopes": {
            "type": "array",
            "items": { "type": "string", "example": "guard.domain.list" },
            "description": "权限范围（perm full key）。空数组 = 完整继承当前用户 RBAC；非空时请求权限必须命中其中之一（精确匹配，不展开通配符）。"
          },
          "expires_in_days": {
            "type": "integer",
            "default": 90,
            "maximum": 365,
            "description": "过期天数，默认 90，最大 365。"
          }
        }
      },
      "CreateAPIKeyResponse": {
        "type": "object",
        "properties": {
          "key_id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "api_key": {
            "type": "string",
            "example": "zck_abc12345.A1b2C3d4E5f6G7h8I9j0K1L2M3n4O5p6Q7r8S9t0",
            "description": "完整明文 API Key，仅在签发响应中出现一次，请立即妥善保存。"
          },
          "prefix": { "type": "string", "example": "zck_abc12345" },
          "last4": { "type": "string", "example": "5t0" },
          "expires_at": { "type": "integer", "format": "int64", "description": "Unix milliseconds" }
        }
      },
      "APIKeyListItem": {
        "type": "object",
        "properties": {
          "key_id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "prefix": { "type": "string" },
          "last4": { "type": "string" },
          "user_id": { "type": "string", "format": "uuid" },
          "oem_id": { "type": "string", "format": "uuid" },
          "scopes": { "type": "array", "items": { "type": "string" } },
          "status": {
            "type": "integer",
            "enum": [1, 2],
            "description": "1=active, 2=revoked"
          },
          "expires_at": { "type": "integer", "format": "int64" },
          "last_used_at": { "type": "integer", "format": "int64" },
          "last_used_ip": { "type": "string" },
          "ctime": { "type": "integer", "format": "int64" }
        }
      },
      "APIKeyAuditLogItem": {
        "type": "object",
        "description": "API Key 审计日志条目，统一用于调用流水（E12）与管理操作流水（E13）。",
        "properties": {
          "id": { "type": "integer", "format": "int64" },
          "event_type": { "type": "string", "enum": ["call", "manage"], "description": "call=调用流水（中间件钩子写入）；manage=管理操作流水（service.api_key 写入）。" },
          "key_id": { "type": "string", "format": "uuid", "description": "关联 API Key ID；批量管理动作（revoke-all）下可空。" },
          "user_id": { "type": "string", "format": "uuid", "description": "操作主体：call 类型为 key 持有人；manage 类型为操作人。" },
          "oem_id": { "type": "string", "format": "uuid" },
          "auth_mode": { "type": "string", "enum": ["apikey", "session"], "description": "请求通过的认证通道。" },
          "action": { "type": "string", "description": "call 类型: '<METHOD> <PATH>' 简化串；manage 类型: 'create' / 'revoke' / 'renew' / 'revoke-all'。" },
          "status_code": { "type": "integer", "description": "HTTP 状态码（call 类型有效）" },
          "biz_code": { "type": "integer", "description": "业务错码（0 = 成功；call 类型有效）" },
          "client_ip": { "type": "string" },
          "user_agent": { "type": "string", "description": "≤ 255 字符，超长截断" },
          "extra": { "type": "string", "description": "JSON 字符串，承载扩展字段（续期前后过期时间、批量动作影响数等）" },
          "ctime": { "type": "integer", "format": "int64", "description": "Unix milliseconds" }
        }
      },
      "APIKeyStatsResponse": {
        "type": "object",
        "description": "单 API Key 的聚合统计结果（仅基于 event_type=call）。",
        "properties": {
          "total_calls": { "type": "integer", "format": "int64", "description": "窗口内总调用次数" },
          "success": { "type": "integer", "format": "int64", "description": "200 <= status_code < 400 的次数" },
          "client_err": { "type": "integer", "format": "int64", "description": "400 <= status_code < 500 的次数" },
          "server_err": { "type": "integer", "format": "int64", "description": "status_code >= 500 的次数" },
          "top_endpoints": {
            "type": "array",
            "description": "调用次数 Top 5 的 action（按次数降序）",
            "items": {
              "type": "object",
              "properties": {
                "action": { "type": "string" },
                "count": { "type": "integer", "format": "int64" }
              }
            }
          },
          "last_1h_qps": { "type": "number", "format": "float", "description": "最近 1 小时窗口的 QPS（次数 / 3600）" }
        }
      },
      "AnalyticsBatchResp": {
        "type": "object",
        "description": "批量 chart 响应。data 的每个 value 严格遵循 BatchChartResult（chart_key/render_hint/schema/rows/meta 5 字段）；meta 为批次级元信息。",
        "properties": {
          "data": {
            "type": "object",
            "description": "key=chart-key，value=BatchChartResult",
            "additionalProperties": { "$ref": "#/components/schemas/BatchChartResult" }
          },
          "meta": {
            "type": "object",
            "properties": {
              "elapsed_ms": { "type": "integer" },
              "cache_hit_ratio": { "type": "number" },
              "total_charts": { "type": "integer" },
              "failed_charts": { "type": "integer" },
              "window": { "$ref": "#/components/schemas/AnalyticsWindow" }
            }
          }
        }
      },
      "PageData": {
        "type": "object",
        "description": "分页响应负载（与 pkg/response.PageData 对齐）。",
        "properties": {
          "list": { "type": "array", "items": {} },
          "total": { "type": "integer", "format": "int64" },
          "page": { "type": "integer" },
          "size": { "type": "integer" }
        }
      },
      "DomainListReq": {
        "type": "object",
        "description": "域名列表查询参数（query/form）。",
        "properties": {
          "page": { "type": "integer", "default": 1, "minimum": 1 },
          "size": { "type": "integer", "default": 20, "maximum": 100 },
          "keyword": { "type": "string" },
          "audit_status": { "type": "integer", "enum": [1, 2, 3, 4], "description": "1=未审核 2=审核中 3=未通过 4=通过" }
        }
      },
      "DomainCreateReq": {
        "type": "object",
        "required": ["domain"],
        "properties": {
          "domain": { "type": "string", "example": "example.com" },
          "asset_name": { "type": "string" },
          "policy_id": { "type": "string", "description": "关联策略 ID（PolicyID 字符串）" }
        }
      },
      "DomainUpdateReq": {
        "type": "object",
        "properties": {
          "asset_name": { "type": "string" },
          "policy_id": { "type": "string" },
          "auto_cert": { "type": "boolean" },
          "mode": { "type": "integer", "format": "int32" }
        }
      },
      "DomainSettingsUpdateReq": {
        "type": "object",
        "required": ["settings"],
        "properties": {
          "settings": {
            "type": "object",
            "description": "settings 是字符串映射 map[string]string，key 取自 GET /domains/:id/settings 返回的 settings.* keys；典型 key 为 waf/cc/acl/bot/cache 等。",
            "additionalProperties": { "type": "string" }
          }
        }
      },
      "DomainVO": {
        "type": "object",
        "properties": {
          "domain_id": { "type": "string" },
          "domain": { "type": "string" },
          "asset_name": { "type": "string" },
          "user_id": { "type": "string", "description": "归属用户 UUID（保留兼容老平台）" },
          "user_name": { "type": "string", "description": "归属用户名（P1.3 新增，来源 cloud sys.users.user_name）" },
          "policy_id": { "type": "string" },
          "cname": { "type": "string" },
          "auto_cert": { "type": "boolean" },
          "mode": { "type": "integer", "format": "int32" },
          "audit_status": { "type": "integer", "format": "int32", "enum": [1, 2, 3, 4], "description": "1=未审核 2=审核中 3=未通过 4=通过" },
          "switches": { "type": "object", "description": "各模块开关（waf/cc/acl/bot/cache 等）", "additionalProperties": { "type": "integer", "format": "int32" } },
          "ctime": { "type": "integer", "format": "int64" },
          "utime": { "type": "integer", "format": "int64" }
        }
      },
      "CertUploadReq": {
        "type": "object",
        "description": "上传证书（**Content-Type 为 application/json**，不是 multipart/form-data）。安全提示：直接集成时 cert/key 是 PEM 文本；不要把私钥写入 AI 对话、工单、日志或可观测埋点。通过 Aegeon Cloud 对话助手操作时应优先使用安全证书附件。",
        "required": ["name", "cert", "key"],
        "properties": {
          "name": { "type": "string", "example": "example.com-2026" },
          "cert": { "type": "string", "description": "PEM 证书文本" },
          "key": { "type": "string", "description": "PEM 私钥文本" },
          "sign_cert": { "type": "string", "description": "国密双证书 - 签名证书 PEM（可选）" },
          "sign_key": { "type": "string", "description": "国密双证书 - 签名私钥 PEM（可选）" }
        }
      },
      "CertUpdateReq": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "cert": { "type": "string" },
          "key": { "type": "string" },
          "sign_cert": { "type": "string" },
          "sign_key": { "type": "string" }
        }
      },
      "CertBindReq": {
        "type": "object",
        "required": ["domain_id"],
        "properties": {
          "domain_id": { "type": "string" }
        }
      },
      "CertVO": {
        "type": "object",
        "description": "P1.3 调整：删除 user_id 字段（避免暴露内部 UUID），新增 user_name 友好展示。",
        "properties": {
          "id": { "type": "integer", "format": "int64" },
          "name": { "type": "string" },
          "user_name": { "type": "string", "description": "归属用户名（P1.3 替换 user_id，来源 cloud sys.users.user_name）" },
          "certificate_type": { "type": "integer", "format": "int32" },
          "common_name": { "type": "string" },
          "issuer": { "type": "string" },
          "expired_at": { "type": "integer", "format": "int64", "description": "Unix milliseconds" },
          "auto_cert": { "type": "boolean" },
          "ctime": { "type": "integer", "format": "int64" },
          "utime": { "type": "integer", "format": "int64" }
        }
      },
      "CertDetailVO": {
        "allOf": [
          { "$ref": "#/components/schemas/CertVO" },
          {
            "type": "object",
            "properties": {
              "cert": { "type": "string", "description": "PEM 证书文本" },
              "sign_cert": { "type": "string", "description": "国密签名证书 PEM" }
            }
          }
        ]
      },
      "CertDomainVO": {
        "type": "object",
        "properties": {
          "domain_id": { "type": "string" },
          "domain": { "type": "string" },
          "cert_id": { "type": "integer", "format": "int64" },
          "ctime": { "type": "integer", "format": "int64" }
        }
      },
      "PolicyListReq": {
        "type": "object",
        "properties": {
          "page": { "type": "integer", "default": 1 },
          "size": { "type": "integer", "default": 20, "maximum": 100 },
          "keyword": { "type": "string" }
        }
      },
      "PolicyCreateReq": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "name": { "type": "string" },
          "comment": { "type": "string", "description": "字段名是 comment（不是 remark）" }
        }
      },
      "PolicyUpdateReq": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "comment": { "type": "string" }
        }
      },
      "PolicyVO": {
        "type": "object",
        "properties": {
          "policy_id": { "type": "string" },
          "name": { "type": "string" },
          "comment": { "type": "string" },
          "user_id": { "type": "string", "description": "归属用户 UUID（保留兼容老平台）" },
          "user_name": { "type": "string", "description": "归属用户名（P1.3 新增，来源 cloud sys.users.user_name）" },
          "default_main_rule_version": { "type": "integer" },
          "is_default": { "type": "boolean" },
          "schema_id": { "type": "integer" },
          "cc_rule_count": { "type": "integer", "format": "int32" },
          "bwl_rule_count": { "type": "integer", "format": "int32" },
          "acl_rule_count": { "type": "integer", "format": "int32" },
          "ctime": { "type": "integer", "format": "int64" },
          "utime": { "type": "integer", "format": "int64" }
        }
      },
      "CcMatch": {
        "type": "object",
        "description": "CC 匹配条件（来自 model.CcMatche）",
        "properties": {
          "field": { "type": "string", "description": "如 uri / host / ip / header" },
          "op":    { "type": "string", "description": "如 eq / ne / contains / regex" },
          "data":  { "type": "string", "description": "匹配值；后端会自动 base64 编码存储，请求时传明文。" }
        }
      },
      "CcRuleCreateReq": {
        "type": "object",
        "required": ["name", "matches", "limit", "action"],
        "properties": {
          "name":     { "type": "string" },
          "describe": { "type": "string" },
          "matches":  { "type": "array", "minItems": 1, "items": { "$ref": "#/components/schemas/CcMatch" } },
          "stats":    { "type": "object", "description": "统计配置（model.CcStats）", "properties": { "name": { "type": "string" }, "data": { "type": "string" } } },
          "limit":    { "type": "object", "description": "限速配置（model.CcLimit），含 threshold/period/scope 等内部字段。建议先调 list 接口取一条样本，按其字段结构提交。", "additionalProperties": true },
          "action":   { "type": "object", "description": "动作配置（model.CcAction），含 action_type/duration/content 等内部字段。", "additionalProperties": true },
          "stime":    { "type": "integer", "format": "int64" },
          "etime":    { "type": "integer", "format": "int64" }
        }
      },
      "CcRuleStatusReq": {
        "type": "object",
        "required": ["status"],
        "properties": {
          "status": { "type": "integer", "format": "int32", "enum": [1, 2], "description": "1=启用 2=禁用（**整数枚举**，不是 enabled/disabled 字符串）" }
        }
      },
      "CcRuleVO": {
        "type": "object",
        "properties": {
          "rule_id":  { "type": "integer", "format": "int64" },
          "name":     { "type": "string" },
          "describe": { "type": "string" },
          "matches":  { "type": "array", "items": { "$ref": "#/components/schemas/CcMatch" } },
          "stats":    { "type": "object", "additionalProperties": true },
          "limit":    { "type": "object", "additionalProperties": true },
          "action":   { "type": "object", "additionalProperties": true },
          "stime":    { "type": "integer", "format": "int64" },
          "etime":    { "type": "integer", "format": "int64" },
          "status":   { "type": "integer", "format": "int32", "enum": [1, 2] },
          "ctime":    { "type": "integer", "format": "int64" },
          "utime":    { "type": "integer", "format": "int64" }
        }
      },
      "AclMatch": {
        "type": "object",
        "description": "ACL 匹配条件（来自 model.AclMatche，无 base64 编码）",
        "properties": {
          "field": { "type": "string" },
          "op":    { "type": "string" },
          "data":  { "type": "string" }
        }
      },
      "AclAction": {
        "type": "object",
        "description": "ACL 动作（model.AclAction）。当 action_type=block/page 时 content 会被自动 base64 编码存储。",
        "properties": {
          "action_type": { "type": "string", "enum": ["block", "allow", "page", "monitor"], "description": "block/page 类型 content 字段会被服务端 base64 编码存储" },
          "content":     { "type": "string" },
          "duration":    { "type": "integer", "format": "int64" }
        }
      },
      "AclRuleCreateReq": {
        "type": "object",
        "required": ["name", "matches", "action"],
        "properties": {
          "name":     { "type": "string" },
          "describe": { "type": "string" },
          "matches":  { "type": "array", "minItems": 1, "items": { "$ref": "#/components/schemas/AclMatch" } },
          "action":   { "$ref": "#/components/schemas/AclAction" },
          "stime":    { "type": "integer", "format": "int64" },
          "etime":    { "type": "integer", "format": "int64" }
        }
      },
      "AclRuleStatusReq": {
        "type": "object",
        "required": ["status"],
        "properties": {
          "status": { "type": "integer", "format": "int32", "enum": [1, 2], "description": "1=启用 2=禁用" }
        }
      },
      "AclRuleVO": {
        "type": "object",
        "properties": {
          "rule_id":  { "type": "integer", "format": "int64" },
          "name":     { "type": "string" },
          "describe": { "type": "string" },
          "matches":  { "type": "array", "items": { "$ref": "#/components/schemas/AclMatch" } },
          "action":   { "$ref": "#/components/schemas/AclAction" },
          "stime":    { "type": "integer", "format": "int64" },
          "etime":    { "type": "integer", "format": "int64" },
          "status":   { "type": "integer", "format": "int32", "enum": [1, 2] },
          "ctime":    { "type": "integer", "format": "int64" },
          "utime":    { "type": "integer", "format": "int64" }
        }
      },
      "BWListSetCreateReq": {
        "type": "object",
        "required": ["name", "ip_set_type"],
        "properties": {
          "name":        { "type": "string" },
          "ip_set_type": { "type": "integer", "format": "int32", "enum": [1, 2, 3, 4], "description": "1=黑名单 2=白名单 3=灰黑名单 4=灰白名单" },
          "policy_id":   { "type": "string" },
          "status":      { "type": "integer", "format": "int32", "enum": [1, 2], "description": "1=禁用 2=启用" },
          "describe":    { "type": "string" }
        }
      },
      "BWListSetUpdateReq": {
        "type": "object",
        "properties": {
          "name":     { "type": "string" },
          "status":   { "type": "integer", "format": "int32", "enum": [1, 2] },
          "describe": { "type": "string" }
        }
      },
      "IPSetVO": {
        "type": "object",
        "description": "P1.3 调整：删除 user_id 字段（避免暴露内部 UUID），新增 user_name 友好展示。",
        "properties": {
          "id":           { "type": "integer", "format": "int64" },
          "user_name":    { "type": "string", "description": "归属用户名（P1.3 替换 user_id，来源 cloud sys.users.user_name）" },
          "name":         { "type": "string" },
          "policy_id":    { "type": "string" },
          "ip_set_type":  { "type": "integer", "format": "int32", "enum": [1, 2, 3, 4] },
          "status":       { "type": "integer", "format": "int32" },
          "count":        { "type": "integer", "format": "int64" },
          "enable_count": { "type": "integer", "format": "int64" },
          "unable_count": { "type": "integer", "format": "int64" },
          "describe":     { "type": "string" },
          "is_default":   { "type": "boolean" },
          "is_private":   { "type": "boolean" },
          "private_domain_id": { "type": "string" },
          "ctime":        { "type": "integer", "format": "int64" },
          "utime":        { "type": "integer", "format": "int64" }
        }
      },
      "BWListIPAddReq": {
        "type": "object",
        "required": ["ip_addr"],
        "properties": {
          "ip_addr": { "type": "string", "example": "192.168.1.1", "description": "IPv4/IPv6 或 CIDR；**字段名为 ip_addr 不是 ip**。" },
          "status":  { "type": "boolean", "description": "默认 true=启用" }
        }
      },
      "BWListIPBatchAddReq": {
        "type": "object",
        "required": ["ips"],
        "properties": {
          "ips": {
            "type": "array",
            "minItems": 1,
            "items": { "$ref": "#/components/schemas/BWListIPAddReq" }
          }
        }
      },
      "BWListIPBatchDeleteReq": {
        "type": "object",
        "required": ["ip_ids"],
        "properties": {
          "ip_ids": {
            "type": "array",
            "minItems": 1,
            "items": { "type": "integer", "format": "int64" },
            "description": "要删除的 IP 条目 ID 列表；删除范围限定在集合 {id} 内，不属于该集合的 ID 会被忽略。"
          }
        }
      },
      "IPVO": {
        "type": "object",
        "properties": {
          "id":      { "type": "integer", "format": "int64" },
          "ipset_id":{ "type": "integer", "format": "int64" },
          "ip_addr": { "type": "string" },
          "status":  { "type": "boolean" },
          "ctime":   { "type": "integer", "format": "int64" },
          "utime":   { "type": "integer", "format": "int64" }
        }
      },
      "ForwardCreateReq": {
        "type": "object",
        "description": "**端口转发**（TCP/UDP）：将公网 port 流量转发到 node_ipaddrs 列表。**不是路径转发**；没有 source_path/target 字段。",
        "required": ["domain", "domain_id", "port"],
        "properties": {
          "user_id":      { "type": "string" },
          "domain":       { "type": "string" },
          "domain_id":    { "type": "string" },
          "schema":       { "type": "integer", "format": "int32", "enum": [3, 4], "description": "3=TCP 4=UDP（默认 3）" },
          "port":         { "type": "integer", "format": "int32", "minimum": 1, "maximum": 65535 },
          "node_ipaddrs": { "type": "string", "description": "源 IP 列表，逗号分隔" },
          "describe":     { "type": "string" },
          "status":       { "type": "integer", "format": "int32", "enum": [1, 2], "description": "1=禁用 2=启用（默认 2）" },
          "src_setting":  { "type": "object", "additionalProperties": true },
          "adv_settings": { "type": "object", "additionalProperties": true },
          "node_setting": { "type": "object", "additionalProperties": true },
          "dev_setting":  { "type": "string" }
        }
      },
      "ForwardUpdateReq": {
        "allOf": [{ "$ref": "#/components/schemas/ForwardCreateReq" }],
        "description": "全部字段可选（不要求 domain/domain_id/port 必填）。"
      },
      "ForwardVO": {
        "type": "object",
        "properties": {
          "id":           { "type": "integer", "format": "int64" },
          "user_id":      { "type": "string", "description": "归属用户 UUID（保留兼容老平台）" },
          "user_name":    { "type": "string", "description": "归属用户名（P1.3 新增，来源 cloud sys.users.user_name）" },
          "domain":       { "type": "string" },
          "domain_id":    { "type": "string" },
          "schema":       { "type": "integer", "format": "int32", "enum": [3, 4] },
          "port":         { "type": "integer", "format": "int32" },
          "node_ipaddrs": { "type": "string" },
          "describe":     { "type": "string" },
          "status":       { "type": "integer", "format": "int32" },
          "src_setting":  { "type": "object", "additionalProperties": true },
          "adv_settings": { "type": "object", "additionalProperties": true },
          "node_setting": { "type": "object", "additionalProperties": true },
          "dev_setting":  { "type": "string" },
          "ctime":        { "type": "integer", "format": "int64" },
          "utime":        { "type": "integer", "format": "int64" }
        }
      },
      "ScheduleDomainListReq": {
        "type": "object",
        "description": "DNS 调度域名列表查询参数。",
        "properties": {
          "page":    { "type": "integer", "default": 1 },
          "size":    { "type": "integer", "default": 20, "maximum": 100 },
          "keyword": { "type": "string",  "description": "按域名模糊搜索" },
          "user_id": { "type": "string",  "description": "按归属用户过滤（仅超管/总代有效）" },
          "mode":    { "type": "integer", "format": "int32", "enum": [0, 1, 2], "description": "0=全部 1=源站(SRC) 2=节点(NODE)" }
        }
      },
      "ScheduleDomainVO": {
        "type": "object",
        "description": "DNS 调度域名视图。`dns_group_type` 取自 guard_db.dns_records（真值源），`parsing_state` 取自 guard_db.guard_configs（前端用于显示\"同步中\" badge）。",
        "properties": {
          "domain_id":          { "type": "string", "description": "域名 ID（guard_configs.domain_id）" },
          "domain_name":        { "type": "string" },
          "user_id":            { "type": "string" },
          "user_name":          { "type": "string" },
          "parsing_state":      { "type": "integer", "format": "int32", "enum": [1, 2], "description": "guard_configs.parsing_state（1=SRC / 2=NODE）" },
          "dns_group_type":     { "type": "integer", "format": "int32", "enum": [1, 2], "description": "dns_records.group_type 多数票（1=SRC / 2=NODE，真值源）" },
          "src_count":          { "type": "integer", "description": "group_type=1 的解析记录条数" },
          "node_count":         { "type": "integer", "description": "group_type=2 的解析记录条数" },
          "src_records":        { "type": "array", "description": "group_type=1 的源站解析摘要，用于列表直接展示当前启停状态", "items": { "$ref": "#/components/schemas/DNSRecordSummaryVO" } },
          "node_records":       { "type": "array", "description": "group_type=2 的节点解析摘要，用于列表直接展示当前启停状态", "items": { "$ref": "#/components/schemas/DNSRecordSummaryVO" } },
          "last_affair_status":  { "type": "string",  "description": "最近一条事务状态字符串（AffairsStatus_Start/Succeed/Faild）" },
          "last_affair_ctime":   { "type": "integer", "format": "int64", "description": "最近一条事务创建时间（毫秒）" },
          "last_affair_message": { "type": "string",  "description": "最近一条事务消息（HTML 片段）" }
        }
      },
      "DNSRecordSummaryVO": {
        "type": "object",
        "description": "DNS 解析记录摘要。",
        "properties": {
          "record_id":   { "type": "string" },
          "subdomain":   { "type": "string" },
          "record_type": { "type": "integer", "format": "int32", "description": "1=A 2=AAAA 3=CNAME 4=MX 5=TXT" },
          "record_line": { "type": "integer", "format": "int32" },
          "value":       { "type": "string" },
          "status":      { "type": "integer", "format": "int32", "enum": [1, 2], "description": "1=禁用 2=启用" }
        }
      },
      "ScheduleDomainListResp": {
        "type": "object",
        "properties": {
          "list":  { "type": "array", "items": { "$ref": "#/components/schemas/ScheduleDomainVO" } },
          "total": { "type": "integer", "format": "int64" }
        }
      },
      "ScheduleSwitchModeReq": {
        "type": "object",
        "description": "切换域名解析模式：批量更新该域名下所有 dns_records 的 group_type 并落事务，异步通过 NSQ topic=`dns` 通知 zdns 服务生效。",
        "required": ["target_mode"],
        "properties": {
          "target_mode": { "type": "integer", "format": "int32", "enum": [1, 2], "description": "1=源站(SRC) 2=节点(NODE)" },
          "comment":     { "type": "string", "description": "事务备注（写入 dns_affairs.message）" }
        }
      },
      "ScheduleInitReq": {
        "type": "object",
        "description": "初始化域名解析：按 guard_domain_settings 与 domain_node_ships 的当前配置重建该域名 dns_records，再落事务并通过 NSQ 通知 zdns 同步一次。",
        "properties": {
          "comment": { "type": "string" }
        }
      },
      "ScheduleResetReq": {
        "type": "object",
        "description": "重置域名解析：将所有 dns_records 的 switch_state 归位 1、status 回滚到 last_status；异步通过 NSQ 通知。",
        "properties": {
          "comment": { "type": "string" }
        }
      },
      "DnsRecordVO": {
        "type": "object",
        "description": "单条 DNS 解析记录视图（直查 guard_db.dns_records）。",
        "properties": {
          "record_id":    { "type": "string" },
          "domain_id":    { "type": "string" },
          "associated_id": { "type": "string", "description": "DNS 服务商侧解析记录 ID" },
          "domain":       { "type": "string" },
          "subdomain":    { "type": "string" },
          "record_type":  { "type": "integer", "format": "int32", "description": "DNS 记录类型（A/AAAA/CNAME 等内部编码）" },
          "record_line":  { "type": "integer", "format": "int32" },
          "value":        { "type": "string", "description": "解析值（IP 或 CNAME 目标）" },
          "ttl":          { "type": "integer", "format": "int64" },
          "status":       { "type": "integer", "format": "int32", "enum": [1, 2], "description": "1=禁用 2=启用" },
          "last_status":  { "type": "integer", "format": "int32" },
          "group_type":   { "type": "integer", "format": "int32", "enum": [1, 2], "description": "1=源站(SRC) 2=节点(NODE)" },
          "switch_state": { "type": "integer", "format": "int32", "description": "1=就绪 2=切换中" },
          "ctime":        { "type": "integer", "format": "int64" },
          "utime":        { "type": "integer", "format": "int64" }
        }
      },
      "ScheduleRecordsResp": {
        "type": "object",
        "properties": {
          "list":  { "type": "array", "items": { "$ref": "#/components/schemas/DnsRecordVO" } },
          "total": { "type": "integer", "format": "int64" }
        }
      },
      "ScheduleBatchStatusReq": {
        "type": "object",
        "description": "批量启停 DNS 解析记录。",
        "required": ["record_ids", "status"],
        "properties": {
          "record_ids": { "type": "array", "items": { "type": "string" }, "description": "目标 dns_records.record_id 集合" },
          "status":     { "type": "integer", "format": "int32", "enum": [1, 2], "description": "1=禁用 2=启用" },
          "comment":    { "type": "string" }
        }
      },
      "ScheduleAffairVO": {
        "type": "object",
        "description": "DNS 调度事务记录（guard_db.dns_affairs）。`status` / `affairs_oper` 序列化为字符串枚举（对齐老平台 MarshalJSON 行为）。",
        "properties": {
          "affairs_id":    { "type": "string",  "description": "事务 ID，格式 `{ts}_{rand10}`" },
          "user_id":       { "type": "string" },
          "user_name":     { "type": "string" },
          "status":        { "type": "string",  "enum": ["AffairsStatus_Start", "AffairsStatus_Succeed", "AffairsStatus_Faild"], "description": "事务状态字符串枚举" },
          "status_code":   { "type": "integer", "format": "int32", "enum": [1, 2, 3], "description": "事务状态数字码（兼容排障/CLI）" },
          "message":       { "type": "string",  "description": "事务消息，含 HTML 片段（如 `<br>`）" },
          "content":       { "type": "string",  "description": "受影响 domain_id，逗号分隔" },
          "json_content":  { "type": "object",  "additionalProperties": true, "nullable": true, "description": "扩展 JSON，含 outbox 重试计数等" },
          "affairs_oper":  { "type": "string",  "enum": ["AffairsOperType_Page", "AffairsOperType_Cron"], "description": "事务发起方" },
          "affairs_oper_code": { "type": "integer", "format": "int32", "enum": [1, 2], "description": "事务发起方数字码" },
          "ctime":         { "type": "integer", "format": "int64" },
          "utime":         { "type": "integer", "format": "int64" }
        }
      },
      "ScheduleAffairListReq": {
        "type": "object",
        "description": "事务记录分页查询。`domain_id` 过滤走 `content LIKE '%'||domain_id||'%'`。",
        "properties": {
          "page":       { "type": "integer", "default": 1 },
          "size":       { "type": "integer", "default": 20, "maximum": 100 },
          "user_id":    { "type": "string" },
          "status":     { "type": "string", "enum": ["AffairsStatus_Start", "AffairsStatus_Succeed", "AffairsStatus_Faild"] },
          "ctime_from": { "type": "integer", "format": "int64", "description": "起始时间毫秒戳" },
          "ctime_to":   { "type": "integer", "format": "int64", "description": "结束时间毫秒戳" },
          "domain_id":  { "type": "string", "description": "按 domain_id 过滤（content LIKE）" }
        }
      },
      "ScheduleAffairListResp": {
        "type": "object",
        "properties": {
          "list":  { "type": "array", "items": { "$ref": "#/components/schemas/ScheduleAffairVO" } },
          "total": { "type": "integer", "format": "int64" }
        }
      },
      "WafRuleReq": {
        "type": "object",
        "description": "WAF 子规则请求体。",
        "properties": {
          "rule_id":      { "type": "integer", "format": "int64", "description": "业务编号，不传由后端生成" },
          "zone":         { "type": "string",  "description": "匹配区域（URL / ARGS / HEADER / BODY 等）" },
          "sub_field":    { "type": "string",  "description": "区域内的子字段，如 header name" },
          "pattern_type": { "type": "integer", "format": "int32", "description": "匹配类型内部编码（精确 / 正则 / 包含等）" },
          "pattern":      { "type": "string",  "description": "匹配表达式" },
          "describe":     { "type": "string" },
          "is_not":       { "type": "boolean", "description": "true = 取反匹配" }
        }
      },
      "WafRuleVO": {
        "type": "object",
        "description": "WAF 子规则视图。",
        "properties": {
          "tag":          { "type": "integer", "format": "int64", "description": "子规则主键（uint64）" },
          "rule_id":      { "type": "integer", "format": "int64" },
          "group_id":     { "type": "integer", "format": "int64", "description": "所属规则组（关联 WafGroupVO.tag / rule_id）" },
          "zone":         { "type": "string" },
          "sub_field":    { "type": "string" },
          "pattern_type": { "type": "integer", "format": "int32" },
          "pattern":      { "type": "string" },
          "describe":     { "type": "string" },
          "is_not":       { "type": "boolean" },
          "ctime":        { "type": "integer", "format": "int64" },
          "utime":        { "type": "integer", "format": "int64" }
        }
      },
      "WafGroupVO": {
        "type": "object",
        "description": "WAF 规则组视图。`tag` 是路径 `{id}` 取值（uint64 主键），与业务编号 `rule_id` 不同。",
        "properties": {
          "tag":                { "type": "integer", "format": "int64", "description": "规则组主键（uint64，路径 {id} 取此字段）" },
          "rule_id":            { "type": "integer", "format": "int64", "description": "业务规则编号" },
          "name":               { "type": "string" },
          "describe":           { "type": "string" },
          "waf_type":           { "type": "integer", "format": "int32", "description": "WAF 类型内部编码" },
          "sub_rule_condition": { "type": "integer", "format": "int32", "description": "子规则组合逻辑（0=AND / 1=OR，按 model 实现为准）" },
          "scope":              { "type": "string",  "description": "作用域（domain / path / 留空 = 全部）" },
          "action":             { "type": "integer", "format": "int32", "enum": [1, 2, 3], "description": "1=block(拦截) 2=log(记录) 3=captcha(验证码)" },
          "status":             { "type": "integer", "format": "int32", "enum": [1, 2], "description": "1=禁用 2=启用（S-6 修复后约定，对齐 forwards/bwlist/schedules）" },
          "policy_id":          { "type": "string",  "description": "所属策略 ID" },
          "rules":              { "type": "array", "items": { "$ref": "#/components/schemas/WafRuleVO" } },
          "ctime":              { "type": "integer", "format": "int64" },
          "utime":              { "type": "integer", "format": "int64" }
        }
      },
      "WafGroupCreateReq": {
        "type": "object",
        "description": "创建 WAF 规则组。`name` 与 `policy_id` 必填；`rules[]` 可空，后续也可通过 update 接口全量替换。",
        "required": ["name", "policy_id"],
        "properties": {
          "rule_id":            { "type": "integer", "format": "int64", "description": "业务编号，不传由后端生成" },
          "name":               { "type": "string" },
          "describe":           { "type": "string" },
          "waf_type":           { "type": "integer", "format": "int32" },
          "sub_rule_condition": { "type": "integer", "format": "int32" },
          "scope":              { "type": "string" },
          "action":             { "type": "integer", "format": "int32", "enum": [1, 2, 3], "description": "1=block 2=log 3=captcha" },
          "status":             { "type": "integer", "format": "int32", "enum": [1, 2], "description": "1=禁用 2=启用（S-6 修复后约定）" },
          "policy_id":          { "type": "string" },
          "rules":              { "type": "array", "items": { "$ref": "#/components/schemas/WafRuleReq" } }
        }
      },
      "WafGroupUpdateReq": {
        "type": "object",
        "description": "更新 WAF 规则组。所有字段可选，按字段增量更新；`rules[]` 全量替换（非增量）；启停切换走独立的 `PUT /{id}/status`，本接口不接受 status。",
        "properties": {
          "name":               { "type": "string" },
          "describe":           { "type": "string" },
          "waf_type":           { "type": "integer", "format": "int32", "nullable": true, "description": "指针类型；未传不动" },
          "sub_rule_condition": { "type": "integer", "format": "int32", "nullable": true, "description": "指针类型；未传不动" },
          "scope":              { "type": "string" },
          "action":             { "type": "integer", "format": "int32", "enum": [1, 2, 3], "nullable": true, "description": "指针类型；未传不动。1=block 2=log 3=captcha" },
          "rules":              { "type": "array", "items": { "$ref": "#/components/schemas/WafRuleReq" }, "description": "全量替换" }
        }
      },
      "WafStatusReq": {
        "type": "object",
        "description": "切换 WAF 规则组启停。`status` 必填且 `oneof=1 2`。",
        "required": ["status"],
        "properties": {
          "status": { "type": "integer", "format": "int32", "enum": [1, 2], "description": "1=禁用 2=启用（S-6 修复后约定，对齐 forwards/bwlist/schedules）" }
        }
      },
      "AlertVO": {
        "type": "object",
        "description": "告警详情视图；底层表结构属于内部实现，不作为 API 契约。",
        "properties": {
          "id":                    { "type": "integer", "format": "int64" },
          "uuid":                  { "type": "string" },
          "policy_type":           { "type": "string" },
          "title":                 { "type": "string" },
          "body":                  { "type": "string" },
          "domain":                { "type": "string" },
          "domain_id":             { "type": "string" },
          "status":                { "type": "integer", "format": "int32", "description": "1=已处理 / 0=待处理（cross-cutting D10 已处理为 1）" },
          "ctime":                 { "type": "integer", "format": "int64" },
          "last_update_timestamp": { "type": "integer", "format": "int64" },
          "process_uid":           { "type": "string" },
          "process_time":          { "type": "integer", "format": "int64" },
          "user_id":               { "type": "string" }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "description": "错误信封，使用 Envelope.code 标识业务错码；data 内可附 category/field/valid_values 帮助调用方做 i18n。",
        "properties": {
          "code":    { "type": "integer", "example": 400, "description": "0=成功；非 0=业务错码" },
          "message": { "type": "string" },
          "data":    {
            "type": "object",
            "properties": {
              "category":     { "type": "string", "enum": ["validation", "auth", "permission", "not_found", "conflict", "rate_limit", "internal"] },
              "field":        { "type": "string", "description": "校验失败的字段名" },
              "valid_values": { "type": "array", "items": {} }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/api/cli/version": {
      "get": {
        "tags": ["cli"],
        "summary": "CLI 版本与多平台下载链接",
        "description": "公开端点，无需认证。客户端调用以发现最新版本及其各 OS/arch 的下载 URL 与 SHA256。",
        "x-permissions": [],
        "x-public": true,
        "responses": {
          "200": {
            "description": "成功",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/CLIVersion" } } }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/cli/download/{filename}": {
      "get": {
        "tags": ["cli"],
        "summary": "下载指定平台二进制",
        "x-permissions": [],
        "x-public": true,
        "parameters": [
          { "name": "filename", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^zcloud-(linux|darwin|windows)-(amd64|arm64)(\\.exe)?$" } }
        ],
        "responses": {
          "200": { "description": "二进制流", "content": { "application/octet-stream": {} } },
          "400": { "description": "文件名非法" },
          "404": { "description": "文件不存在" }
        }
      }
    },
    "/api/cli/install.sh": {
      "get": {
        "tags": ["cli"],
        "summary": "动态渲染的一行安装脚本",
        "description": "Host 头注入到脚本里，客户端 `curl … | sh` 直接安装。",
        "x-permissions": [],
        "x-public": true,
        "responses": {
          "200": { "description": "shell 脚本", "content": { "text/plain": {} } }
        }
      }
    },
    "/api/cli/checksums.txt": {
      "get": {
        "tags": ["cli"],
        "summary": "全平台二进制 SHA256",
        "x-permissions": [],
        "x-public": true,
        "responses": {
          "200": { "description": "纯文本", "content": { "text/plain": {} } }
        }
      }
    },
    "/api/auth/login": {
      "post": {
        "tags": ["auth"],
        "summary": "账号密码登录获取 Bearer Token",
        "x-permissions": [],
        "x-public": true,
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LoginRequest" } } }
        },
        "responses": {
          "200": {
            "description": "成功",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/LoginResponse" } } }
                  ]
                }
              }
            }
          },
          "401": { "description": "用户名或密码错误" }
        }
      }
    },
    "/api/sys/wx-zombies/cleanup": {
      "post": {
        "tags": ["sys.user"],
        "summary": "扫描并可选清理已取关公众号的本地微信僵尸绑定（仅平台管理员）",
        "description": "dry-run（apply=false）只返回候选；apply=true 时执行清理，且必须全量扫描（不传 service_ids）。openid 取令牌使用 stable_token，不影响 notify 在线发消息。",
        "x-permissions": ["sys.user.wx_cleanup"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": {
          "required": false,
          "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "apply": { "type": "boolean", "default": false, "description": "执行清理；默认仅 dry-run 扫描" },
              "service_ids": { "type": "array", "items": { "type": "string" }, "description": "只扫描指定微信服务 ID；apply 时必须为空（要求全量扫描）" },
              "max_pages": { "type": "integer", "default": 20, "description": "每个公众号最多拉取 user/get 页数" },
              "allow_empty_followers": { "type": "boolean", "default": false, "description": "允许关注者列表为空时执行清理" }
            }
          } } }
        },
        "responses": {
          "200": { "description": "成功" },
          "400": { "description": "参数非法 / apply 时传了 service_ids / 关注者列表为空" },
          "403": { "description": "非平台级管理员" }
        }
      }
    },
    "/api/sys/users": {
      "get": {
        "tags": ["sys.user"],
        "summary": "分页查询用户列表",
        "description": "**分页字段为 `page` + `size`**（不是 `page_size`，pkg/request/bind.go 的 PageQuery 真值）。size 上限 100。",
        "x-permissions": ["sys.user.list"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1, "minimum": 1 } },
          { "name": "size", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } },
          { "name": "keyword", "in": "query", "schema": { "type": "string" }, "description": "搜索 user_name/nick_name/email/mobile" }
        ],
        "responses": {
          "200": {
            "description": "成功",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "type": "object", "properties": { "list": { "type": "array", "items": { "$ref": "#/components/schemas/User" } }, "total": { "type": "integer" } } } } }
                  ]
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": ["sys.user"],
        "summary": "创建用户（需 OEM 关联）",
        "x-permissions": ["sys.user.create"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } }
        },
        "responses": {
          "200": { "description": "成功" },
          "400": { "description": "参数非法" }
        }
      }
    },
    "/api/sys/users/{id}": {
      "get": {
        "tags": ["sys.user"],
        "summary": "查询用户详情",
        "x-permissions": ["sys.user.view"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "404": { "description": "用户不存在" }
        }
      }
    },
    "/api/auth/logout": {
      "post": {
        "tags": ["auth"],
        "summary": "登出销毁 session",
        "x-permissions": [],
        "x-auth-required": true,
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": { "description": "成功" }
        }
      }
    },
    "/api/sys/users/{id}/password": {
      "put": {
        "tags": ["sys.user"],
        "summary": "重置用户密码（强制下次登录改密）",
        "x-permissions": ["sys.user.resetpwd"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "properties": { "new_password": { "type": "string", "format": "password" } }, "required": ["new_password"] } } }
        },
        "responses": {
          "200": { "description": "成功" },
          "403": { "description": "权限不足" }
        }
      }
    },
    "/api/sys/api-keys": {
      "get": {
        "tags": ["sys.apikey"],
        "summary": "分页查询当前用户/本 OEM 的 API Key 列表",
        "description": "普通用户（role_id ≥ 10）只看自己签发的；平台级用户（role_id < 10：超管/运维/审计员）看本 OEM 全部。响应不包含 key_hash 与明文。",
        "x-permissions": ["sys.apikey.list"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "size", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } }
        ],
        "responses": {
          "200": {
            "description": "成功",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "type": "object", "properties": { "list": { "type": "array", "items": { "$ref": "#/components/schemas/APIKeyListItem" } }, "total": { "type": "integer" } } } } }
                  ]
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": ["sys.apikey"],
        "summary": "签发新 API Key（明文仅返回一次）",
        "description": "scope 列表必须 ⊆ 当前用户 RBAC 权限边界，超出则返回 1052。明文 api_key 仅在本响应中出现，必须立即保存——后续接口不再返回。",
        "x-permissions": ["sys.apikey.create"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateAPIKeyRequest" } } }
        },
        "responses": {
          "200": {
            "description": "成功",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/CreateAPIKeyResponse" } } }
                  ]
                }
              }
            }
          },
          "400": { "description": "参数非法：name 缺失 (code 1055) / expires_in_days > 365 (code 1054)" },
          "403": { "description": "权限不足：scope 超出用户 RBAC 边界 (code 1052) / 调用方 API Key 自身的 scope 不足以执行该接口 (code 1053, 精确字符串匹配，不展开通配符) / 跨 OEM 越权 (code 1056)" }
        }
      }
    },
    "/api/sys/api-keys/{id}": {
      "delete": {
        "tags": ["sys.apikey"],
        "summary": "吊销 API Key（status=revoked，幂等）",
        "description": "普通用户只能吊销自己签发的；平台级用户可吊销本 OEM 内任意 key。重复吊销返回 200（幂等）。",
        "x-permissions": ["sys.apikey.delete"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": { "description": "成功" },
          "403": { "description": "权限不足：调用方 API Key scope 不足 (code 1053) / 跨 OEM 越权或越权操作他人 key (code 1056)" },
          "404": { "description": "API Key 不存在或不在可见范围（code 1051）" }
        }
      }
    },
    "/api/sys/api-keys/{id}/logs": {
      "get": {
        "tags": ["sys.apikey"],
        "summary": "查询某 Key 的调用流水（E12）/ 管理操作流水（默认 event=call）",
        "description": "返回 api_key_audit_logs 中按 key_id 过滤的事件列表。普通用户仅可查询自己签发的 Key；平台级用户可查询本 OEM 内任意 Key（超管全库）。",
        "x-permissions": ["sys.apikey.logs"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } },
          { "name": "event", "in": "query", "schema": { "type": "string", "enum": ["call", "manage"] }, "description": "事件类型，留空走 call。" },
          { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "size", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } }
        ],
        "responses": {
          "200": {
            "description": "成功",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "type": "object", "properties": { "list": { "type": "array", "items": { "$ref": "#/components/schemas/APIKeyAuditLogItem" } }, "total": { "type": "integer" } } } } }
                  ]
                }
              }
            }
          },
          "404": { "description": "API Key 不存在或不在可见范围（code 1051）" }
        }
      }
    },
    "/api/sys/api-keys/{id}/stats": {
      "get": {
        "tags": ["sys.apikey"],
        "summary": "查询某 Key 的聚合统计（成功率/错误分布/top endpoints/最近1h QPS）",
        "description": "仅基于 event_type=call 的记录聚合。归属校验同 logs 接口；since 留空 = 全量历史。",
        "x-permissions": ["sys.apikey.stats"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } },
          { "name": "since", "in": "query", "schema": { "type": "string" }, "description": "聚合窗口起点：相对值 24h / 7d / 30m，或纯整数 = Unix 毫秒；留空 = 全量历史。" }
        ],
        "responses": {
          "200": {
            "description": "成功",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/APIKeyStatsResponse" } } }
                  ]
                }
              }
            }
          },
          "400": { "description": "since 参数解析失败" },
          "404": { "description": "API Key 不存在或不在可见范围（code 1051）" }
        }
      }
    },
    "/api/sys/api-keys/audit-actions": {
      "get": {
        "tags": ["sys.apikey"],
        "summary": "跨 Key 的管理操作流水（E13）",
        "description": "返回 event_type=manage 的审计记录。普通用户仅看自己作为操作人的记录；平台级用户看本 OEM 全部（超管全库）。",
        "x-permissions": ["sys.apikey.audit"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "size", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } }
        ],
        "responses": {
          "200": {
            "description": "成功",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "type": "object", "properties": { "list": { "type": "array", "items": { "$ref": "#/components/schemas/APIKeyAuditLogItem" } }, "total": { "type": "integer" } } } } }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/guard/domains": {
      "get": {
        "tags": ["guard.domain"],
        "summary": "分页查询域名列表",
        "description": "**分页字段为 `page` + `size`**（不是 page_size）。",
        "x-permissions": ["guard.domain.list"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "size", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } },
          { "name": "keyword", "in": "query", "schema": { "type": "string" } },
          { "name": "audit_status", "in": "query", "schema": { "type": "integer", "enum": [1, 2, 3, 4] }, "description": "1=未审核 2=审核中 3=未通过 4=通过" }
        ],
        "responses": {
          "200": {
            "description": "成功",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "allOf": [{ "$ref": "#/components/schemas/PageData" }, { "type": "object", "properties": { "list": { "type": "array", "items": { "$ref": "#/components/schemas/DomainVO" } } } }] } } }
                  ]
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": ["guard.domain"],
        "summary": "创建域名接入",
        "x-permissions": ["guard.domain.create"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DomainCreateReq" } } }
        },
        "responses": {
          "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/DomainVO" } } } ] } } } },
          "400": { "description": "域名格式非法或 domain 已被占用", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } }
        }
      }
    },
    "/api/analytics/glossary": {
      "get": {
        "tags": ["analytics"],
        "summary": "统计分析术语表",
        "x-permissions": [],
        "x-auth-required": true,
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "responses": {
          "200": {
            "description": "成功",
            "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "type": "object", "properties": { "terms": { "type": "object", "additionalProperties": { "type": "string" } } } } } } ] } } }
          }
        }
      }
    },
    "/api/analytics/batch": {
      "post": {
        "tags": ["analytics"],
        "summary": "跨页统计图表 batch 查询",
        "description": "由请求体 `page` 字段决定鉴权 perm key（analytics.{page}.view），无该 page 权限返回 403。\n\n---\n\n**Chart 统一契约**（docs/specs/chart-contract.md）：\n\n`data[<chart_key>]` 固定为 `BatchChartResult` 形状（仅 5 字段：chart_key / render_hint / schema / rows / meta）。老数据库表、聚合表和 ES 索引只作为内部数据源，不改变对外请求参数或响应结构。",
        "x-permissions": [
          "analytics.overview.view",
          "analytics.access.view",
          "analytics.protect.view",
          "analytics.ai.view",
          "analytics.bot.view",
          "analytics.alert.view",
          "analytics.health.view",
          "analytics.ops.view",
          "analytics.closure.view",
          "analytics.cache.view"
        ],
        "x-permissions-resolution": "by-request-body-page",
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AnalyticsBatchReq" } } }
        },
        "responses": {
          "200": {
            "description": "成功",
            "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/AnalyticsBatchResp" } } } ] } } }
          },
          "403": { "description": "无对应页面权限" }
        }
      }
    },
    "/api/analytics/{page}/batch": {
      "post": {
        "tags": ["analytics"],
        "summary": "页面级统计图表 batch 查询",
        "description": "page 支持 overview/access/protect/ai/bot/alert/health/ops/closure/cache。路径已发布，后续不可改名。鉴权 perm key 由路径变量 `page` 决定（analytics.{page}.view）。\n\n**charts[].key 真值表**（来源 `internal/chart/route.go`，使用横线/斜线分隔，**不是下划线**）：\n\n- **overview**（8）: `kpi` / `bandwidth` / `request-attack` / `event-type` / `waf-type` / `geo` / `top-domains` / `recent-events`\n- **access**（10）: `request-hm` / `flow-hm` / `cache-hm` / `bandwidth` / `status` / `flow-duration` / `isp` / `top-ip` / `top-url` / `geo`\n- **protect**（12）: `overview` / `waf/statistics` / `waf/types` / `waf/top-ip` / `waf/geo` / `cc/statistics` / `cc/top-ip` / `cc/geo` / `cc/top-url` / `ddos/statistics` / `ddos/types` / `ddos/top-ip`\n- **ai**（6）: `attack-trend` / `top-ip` / `top-url` / `detection` / `test-results` / `logs`\n- **bot**（10）: `statistics` / `advance-warn` / `browser` / `operating` / `geo` / `top-agent` / `top-ip` / `scatter` / `sessions` / `session`\n- **alert**（5）: `total` / `hm` / `types` / `domains` / `list`\n- **health**（7）: `summary` / `status-breakdown` / `origin-errors` / `origin-latency` / `slow-uri` / `availability` / `geo-isp-quality`\n- **ops**（8）: `summary` / `traffic-users` / `traffic-domains` / `error-users` / `error-domains` / `origin-errors` / `nodes` / `query-pressure`\n- **closure**（4）: `summary` / `alerts` / `risks` / `trend`\n- **cache**（4）: `summary` / `trend` / `top-uri` / `content-types`\n\n**分页字段为 `page` + `size`**（不是 `page_size`）。\n\n---\n\n**Chart 统一契约**（docs/specs/chart-contract.md）：\n\n所有 chart-key 的响应均以 `BatchChartResult` 为目标形状（仅 5 字段：chart_key / render_hint / schema / rows / meta，禁止 series/totals/kpis/points/list 作为对外顶层字段）。调用方以 `render_hint` 和 `schema` 决定展示方式，不依赖底层数据库字段名。",
        "x-permissions": [
          "analytics.overview.view",
          "analytics.access.view",
          "analytics.protect.view",
          "analytics.ai.view",
          "analytics.bot.view",
          "analytics.alert.view",
          "analytics.health.view",
          "analytics.ops.view",
          "analytics.closure.view",
          "analytics.cache.view"
        ],
        "x-permissions-resolution": "by-path-page",
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "page", "in": "path", "required": true, "schema": { "type": "string", "enum": ["overview", "access", "protect", "ai", "bot", "alert", "health", "ops", "closure", "cache"] } }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AnalyticsBatchReq" } } }
        },
        "responses": {
          "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/AnalyticsBatchResp" } } } ] } } } },
          "403": { "description": "无对应页面权限" }
        }
      }
    },
    "/api/analytics/{page}/{chart}": {
      "get": {
        "tags": ["analytics"],
        "summary": "单图统计查询",
        "description": "代表现有页面下的单图 GET 接口，例如 /api/analytics/overview/kpi、/api/analytics/access/status、/api/analytics/protect/waf/types。chart 可以包含子路径片段，完整清单见 /docs/api。鉴权 perm key 由路径变量 `page` 决定（analytics.{page}.view）。\n\n---\n\n**Chart 统一契约**（docs/specs/chart-contract.md）：\n\n`data` 固定为 `BatchChartResult` 形状（仅 5 字段：chart_key / render_hint / schema / rows / meta）。老数据库表、聚合表和 ES 索引只作为内部数据源；调用方只依赖路径、查询参数、`render_hint`、`schema` 与 `rows`。",
        "x-permissions": [
          "analytics.overview.view",
          "analytics.access.view",
          "analytics.protect.view",
          "analytics.ai.view",
          "analytics.bot.view",
          "analytics.alert.view",
          "analytics.health.view",
          "analytics.ops.view",
          "analytics.closure.view",
          "analytics.cache.view"
        ],
        "x-permissions-resolution": "by-path-page",
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "page", "in": "path", "required": true, "schema": { "type": "string", "enum": ["overview", "access", "protect", "ai", "bot", "alert", "health", "ops", "closure", "cache"] } },
          { "name": "chart", "in": "path", "required": true, "schema": { "type": "string", "example": "kpi" } },
          { "name": "window", "in": "query", "schema": { "type": "string", "example": "last_24h" } },
          { "name": "stime", "in": "query", "schema": { "type": "integer", "format": "int64" } },
          { "name": "etime", "in": "query", "schema": { "type": "integer", "format": "int64" } },
          { "name": "site_id", "in": "query", "schema": { "type": "string" } },
          { "name": "domain_id", "in": "query", "schema": { "type": "string" } },
          { "name": "target_user_id", "in": "query", "schema": { "type": "string" } },
          { "name": "compare", "in": "query", "schema": { "type": "boolean" } },
          { "name": "top", "in": "query", "schema": { "type": "integer", "maximum": 100 } },
          { "name": "order", "in": "query", "schema": { "type": "string" } },
          { "name": "page", "in": "query", "schema": { "type": "integer" } },
          { "name": "size", "in": "query", "schema": { "type": "integer", "maximum": 100 } }
        ],
        "responses": {
          "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/BatchChartResult" } } } ] } } } },
          "403": { "description": "无对应页面权限" }
        }
      }
    },
    "/api/analytics/overview/export": {
      "post": {
        "tags": ["analytics"],
        "summary": "导出总览图表快照",
        "description": "返回原始 CSV 或 JSON 文本，不使用统一 JSON 信封。",
        "x-permissions": ["analytics.overview.export"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AnalyticsExportReq" } } }
        },
        "responses": {
          "200": {
            "description": "CSV 或 JSON 原文",
            "content": {
              "text/csv": { "schema": { "type": "string" } },
              "application/json": { "schema": { "type": "string" } }
            }
          },
          "403": { "description": "缺少 analytics.overview.export 权限" }
        }
      }
    },
    "/api/analytics/bot/sessions/{sid}": {
      "get": {
        "tags": ["analytics"],
        "summary": "Bot 单会话时间线详情",
        "x-permissions": ["analytics.bot.session"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "sid", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "window", "in": "query", "schema": { "type": "string", "example": "last_24h" } },
          { "name": "stime", "in": "query", "schema": { "type": "integer", "format": "int64" } },
          { "name": "etime", "in": "query", "schema": { "type": "integer", "format": "int64" } }
        ],
        "responses": {
          "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/BatchChartResult" } } } ] } } } },
          "403": { "description": "缺少 analytics.bot.session 权限" }
        }
      }
    },
    "/api/analytics/alert/{id}/ack": {
      "patch": {
        "tags": ["analytics"],
        "summary": "确认告警",
        "x-permissions": ["analytics.alert.ack"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "403": { "description": "缺少 analytics.alert.ack 权限" }
        }
      }
    },
    "/api/analytics/logs": {
      "get": {
        "tags": ["analytics"],
        "summary": "Phase 1 原始日志分页查询",
        "description": "12 ES 字段筛选：uuid/session_id/remote_addr/host/uri/method/status/z_final_mod/z_final_action(int 0/1/2)/z_final_type/z_final_id/z_waf_id/z_cc_id/z_white(独立 bool)。禁字段：match_content/match_area/hit_rule/rule_desc。第一版返回 available:false stub。",
        "x-permissions": ["analytics.logs.view"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "window", "in": "query", "schema": { "type": "string", "example": "last_24h" } },
          { "name": "stime", "in": "query", "schema": { "type": "integer", "format": "int64" } },
          { "name": "etime", "in": "query", "schema": { "type": "integer", "format": "int64" } },
          { "name": "site_id", "in": "query", "schema": { "type": "string" } },
          { "name": "domain_id", "in": "query", "schema": { "type": "string" } },
          { "name": "target_user_id", "in": "query", "schema": { "type": "string" } },
          { "name": "uuid", "in": "query", "schema": { "type": "string" } },
          { "name": "session_id", "in": "query", "schema": { "type": "string" } },
          { "name": "remote_addr", "in": "query", "schema": { "type": "string" } },
          { "name": "host", "in": "query", "schema": { "type": "string" } },
          { "name": "uri", "in": "query", "schema": { "type": "string" } },
          { "name": "method", "in": "query", "schema": { "type": "string" } },
          { "name": "status", "in": "query", "schema": { "type": "integer" } },
          { "name": "z_final_mod", "in": "query", "schema": { "type": "string", "example": "mod_waf" } },
          { "name": "z_final_action", "in": "query", "schema": { "type": "integer", "enum": [0, 1, 2], "description": "0=放行 1=拦截 2=验证码" } },
          { "name": "z_final_type", "in": "query", "schema": { "type": "string" } },
          { "name": "z_final_id", "in": "query", "schema": { "type": "string" } },
          { "name": "z_waf_id", "in": "query", "schema": { "type": "string" } },
          { "name": "z_cc_id", "in": "query", "schema": { "type": "string" } },
          { "name": "z_white", "in": "query", "schema": { "type": "boolean" } },
          { "name": "page", "in": "query", "schema": { "type": "integer" } },
          { "name": "size", "in": "query", "schema": { "type": "integer", "maximum": 100 } }
        ],
        "responses": {
          "200": { "description": "成功（数据源未接入时返回 available:false + reason）", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "403": { "description": "缺少 analytics.logs.view 权限" }
        }
      }
    },
    "/api/analytics/logs/{uuid}": {
      "get": {
        "tags": ["analytics"],
        "summary": "Phase 1 单条日志详情",
        "description": "返回 7 区块：basic / request / response / upstream / protection / waf_detail / ai_detail。第一版 stub。",
        "x-permissions": ["analytics.logs.view"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "uuid", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "403": { "description": "缺少 analytics.logs.view 权限" }
        }
      }
    },
    "/api/analytics/logs/export": {
      "post": {
        "tags": ["analytics"],
        "summary": "Phase 1 原始日志导出",
        "description": "返回 csv/json 原文（不走 Envelope）。size ≤ 10000，超出走异步任务。fields 字段白名单。",
        "x-permissions": ["analytics.logs.export"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object",
            "required": ["query"],
            "properties": {
              "format": { "type": "string", "enum": ["csv", "json"], "default": "csv" },
              "fields": { "type": "array", "items": { "type": "string" }, "maxItems": 64 },
              "query": { "type": "object", "description": "RawLogQuery（同 GET /api/analytics/logs 入参）" }
            }
          } } }
        },
        "responses": {
          "200": {
            "description": "csv 或 json 原文",
            "content": {
              "text/csv": { "schema": { "type": "string" } },
              "application/json": { "schema": { "type": "string" } }
            }
          },
          "403": { "description": "缺少 analytics.logs.export 权限" }
        }
      }
    },
    "/api/analytics/closure/alerts/confirm": {
      "post": {
        "tags": ["analytics"],
        "summary": "Phase 6 批量确认告警",
        "description": "内部代理 /api/alert/records/confirm。真值字段 process_uid/process_time/status；禁 handle_user/handle_time/alert_status。",
        "x-permissions": ["analytics.closure.confirm"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object", "required": ["ids"],
            "properties": {
              "ids": { "type": "array", "items": { "type": "string" }, "minItems": 1, "maxItems": 200 },
              "remark": { "type": "string" }
            }
          } } }
        },
        "responses": {
          "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "403": { "description": "缺少 analytics.closure.confirm 权限" }
        }
      }
    },
    "/api/analytics/closure/risks/confirm": {
      "post": {
        "tags": ["analytics"],
        "summary": "Phase 6 批量确认风险事件",
        "description": "内部代理 /api/chart/risk/events/:event_id/confirm。RiskRecord 真值字段 level/risk_subject/risk_subject_type/status(1/2)；禁 risk_score。",
        "x-permissions": ["analytics.closure.confirm"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object", "required": ["ids"],
            "properties": {
              "ids": { "type": "array", "items": { "type": "string" }, "minItems": 1, "maxItems": 200 },
              "remark": { "type": "string" }
            }
          } } }
        },
        "responses": {
          "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "403": { "description": "缺少 analytics.closure.confirm 权限" }
        }
      }
    },
    "/api/analytics/reports/templates": {
      "get": {
        "tags": ["analytics"],
        "summary": "Phase 4 报表模板列表",
        "description": "返回 6 模板：protection-value / asset-risk / attack-source / business-health / platform-summary（platform_only） / raw-log-export。",
        "x-permissions": ["analytics.reports.view"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "responses": {
          "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "403": { "description": "缺少 analytics.reports.view 权限" }
        }
      }
    },
    "/api/analytics/reports": {
      "get": {
        "tags": ["analytics"],
        "summary": "Phase 4 报表历史列表",
        "x-permissions": ["analytics.reports.view"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "responses": {
          "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "403": { "description": "缺少 analytics.reports.view 权限" }
        }
      }
    },
    "/api/analytics/reports/{id}": {
      "get": {
        "tags": ["analytics"],
        "summary": "Phase 4 报表详情",
        "x-permissions": ["analytics.reports.view"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "403": { "description": "缺少 analytics.reports.view 权限" }
        }
      }
    },
    "/api/analytics/reports/generate": {
      "post": {
        "tags": ["analytics"],
        "summary": "Phase 4 触发报表生成",
        "description": "同步阈值 ≤ 100k 行；超出强制异步返回 task_id。模板 platform-summary 需 analytics.reports.platform 权限。",
        "x-permissions": ["analytics.reports.generate"],
        "x-additional-permissions": {
          "platform-summary": ["analytics.reports.platform"]
        },
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object", "required": ["template", "window"],
            "properties": {
              "template": { "type": "string", "enum": ["protection-value", "asset-risk", "attack-source", "business-health", "platform-summary", "raw-log-export"] },
              "format": { "type": "string", "enum": ["csv", "json", "pdf", "html"] },
              "window": { "type": "string", "example": "last_30d" },
              "stime": { "type": "integer", "format": "int64" },
              "etime": { "type": "integer", "format": "int64" },
              "filters": { "type": "object", "additionalProperties": true }
            }
          } } }
        },
        "responses": {
          "200": { "description": "成功（生成能力未接入时返回占位状态）", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "403": { "description": "缺少 analytics.reports.generate / analytics.reports.platform 权限" }
        }
      }
    },
    "/api/analytics/reports/{id}/download": {
      "get": {
        "tags": ["analytics"],
        "summary": "Phase 4 下载报表产物",
        "description": "返回 pdf/csv/json/html 原文（不走 Envelope）。",
        "x-permissions": ["analytics.reports.download"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "报表产物原文",
            "content": {
              "application/pdf": { "schema": { "type": "string", "format": "binary" } },
              "text/csv": { "schema": { "type": "string" } },
              "application/json": { "schema": { "type": "string" } },
              "text/html": { "schema": { "type": "string" } }
            }
          },
          "403": { "description": "缺少 analytics.reports.download 权限" }
        }
      }
    },
    "/api/guard/domains/{id}": {
      "get": {
        "tags": ["guard.domain"],
        "summary": "查询域名详情",
        "x-permissions": ["guard.domain.view"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "DomainID（字符串）" }],
        "responses": {
          "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/DomainVO" } } } ] } } } },
          "404": { "description": "域名不存在" }
        }
      },
      "put": {
        "tags": ["guard.domain"],
        "summary": "编辑域名",
        "x-permissions": ["guard.domain.edit"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DomainUpdateReq" } } } },
        "responses": { "200": { "description": "成功" }, "404": { "description": "域名不存在" } }
      },
      "delete": {
        "tags": ["guard.domain"],
        "summary": "删除域名",
        "x-permissions": ["guard.domain.delete"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "成功" }, "404": { "description": "域名不存在" } }
      }
    },
    "/api/guard/domains/{id}/settings": {
      "get": {
        "tags": ["guard.domain"],
        "summary": "查询域名配置",
        "x-permissions": ["guard.domain.settings"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "type": "object", "properties": { "settings": { "type": "object", "additionalProperties": { "type": "string" } } } } } } ] } } } }
        }
      },
      "put": {
        "tags": ["guard.domain"],
        "summary": "更新域名配置",
        "x-permissions": ["guard.domain.settings"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DomainSettingsUpdateReq" } } } },
        "responses": { "200": { "description": "成功" }, "400": { "description": "settings 字段格式非法" } }
      }
    },
    "/api/guard/policies": {
      "get": {
        "tags": ["guard.policy"],
        "summary": "分页查询策略列表",
        "x-permissions": ["guard.policy.list"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "size", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } },
          { "name": "keyword", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "allOf": [{ "$ref": "#/components/schemas/PageData" }, { "type": "object", "properties": { "list": { "type": "array", "items": { "$ref": "#/components/schemas/PolicyVO" } } } }] } } } ] } } } } }
      },
      "post": {
        "tags": ["guard.policy"],
        "summary": "创建策略",
        "x-permissions": ["guard.policy.create"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PolicyCreateReq" } } } },
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/PolicyVO" } } } ] } } } }, "400": { "description": "name 已存在或字段非法" } }
      }
    },
    "/api/guard/policies/{id}": {
      "get": {
        "tags": ["guard.policy"],
        "summary": "查询策略详情",
        "x-permissions": ["guard.policy.view"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "PolicyID（字符串）" }],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/PolicyVO" } } } ] } } } }, "404": { "description": "策略不存在" } }
      },
      "put": {
        "tags": ["guard.policy"],
        "summary": "编辑策略",
        "x-permissions": ["guard.policy.edit"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PolicyUpdateReq" } } } },
        "responses": { "200": { "description": "成功" }, "404": { "description": "策略不存在" } }
      },
      "delete": {
        "tags": ["guard.policy"],
        "summary": "删除策略",
        "x-permissions": ["guard.policy.delete"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "成功" }, "404": { "description": "策略不存在" } }
      }
    },
    "/api/guard/policies/{id}/cc/rules": {
      "get": {
        "tags": ["guard.policy"],
        "summary": "查询策略下 CC 规则列表",
        "x-permissions": ["guard.cc.list"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/CcRuleVO" } } } } ] } } } } }
      },
      "post": {
        "tags": ["guard.policy"],
        "summary": "创建 CC 规则",
        "x-permissions": ["guard.cc.create"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CcRuleCreateReq" } } } },
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/CcRuleVO" } } } ] } } } } }
      }
    },
    "/api/guard/policies/{id}/cc/rules/{rid}": {
      "get": {
        "tags": ["guard.policy"],
        "summary": "查询单条 CC 规则详情",
        "description": "rule_id 必须归属当前 policy_id，否则返回 NotFound（防止跨策略越权读取）。",
        "x-permissions": ["guard.cc.view"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "rid", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }
        ],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/CcRuleVO" } } } ] } } } }, "404": { "description": "规则不存在或不属于该策略" } }
      },
      "put": {
        "tags": ["guard.policy"],
        "summary": "编辑 CC 规则",
        "x-permissions": ["guard.cc.edit"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "rid", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CcRuleCreateReq" } } } },
        "responses": { "200": { "description": "成功" } }
      },
      "delete": {
        "tags": ["guard.policy"],
        "summary": "删除 CC 规则",
        "x-permissions": ["guard.cc.delete"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "rid", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }
        ],
        "responses": { "200": { "description": "成功" } }
      }
    },
    "/api/guard/policies/{id}/cc/rules/{rid}/status": {
      "put": {
        "tags": ["guard.policy"],
        "summary": "切换 CC 规则启停",
        "x-permissions": ["guard.cc.status"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "rid", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CcRuleStatusReq" } } } },
        "responses": { "200": { "description": "成功" }, "400": { "description": "status 必须为 1 或 2" } }
      }
    },
    "/api/guard/policies/{id}/acl/rules": {
      "get": {
        "tags": ["guard.policy"],
        "summary": "查询策略下 ACL 规则列表",
        "x-permissions": ["guard.acl.list"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/AclRuleVO" } } } } ] } } } } }
      },
      "post": {
        "tags": ["guard.policy"],
        "summary": "创建 ACL 规则",
        "x-permissions": ["guard.acl.create"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AclRuleCreateReq" } } } },
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/AclRuleVO" } } } ] } } } } }
      }
    },
    "/api/guard/policies/{id}/acl/rules/{rid}": {
      "get": {
        "tags": ["guard.policy"],
        "summary": "查询单条 ACL 规则详情",
        "description": "rule_id 必须归属当前 policy_id。",
        "x-permissions": ["guard.acl.view"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "rid", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }
        ],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/AclRuleVO" } } } ] } } } }, "404": { "description": "规则不存在或不属于该策略" } }
      },
      "put": {
        "tags": ["guard.policy"],
        "summary": "编辑 ACL 规则",
        "x-permissions": ["guard.acl.edit"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "rid", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AclRuleCreateReq" } } } },
        "responses": { "200": { "description": "成功" } }
      },
      "delete": {
        "tags": ["guard.policy"],
        "summary": "删除 ACL 规则",
        "x-permissions": ["guard.acl.delete"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "rid", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }
        ],
        "responses": { "200": { "description": "成功" } }
      }
    },
    "/api/guard/policies/{id}/acl/rules/{rid}/status": {
      "put": {
        "tags": ["guard.policy"],
        "summary": "切换 ACL 规则启停",
        "x-permissions": ["guard.acl.status"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "rid", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AclRuleStatusReq" } } } },
        "responses": { "200": { "description": "成功" }, "400": { "description": "status 必须为 1 或 2" } }
      }
    },
    "/api/guard/certs": {
      "get": {
        "tags": ["guard.cert"],
        "summary": "分页查询证书列表",
        "x-permissions": ["guard.cert.list"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "size", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } },
          { "name": "keyword", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "allOf": [{ "$ref": "#/components/schemas/PageData" }, { "type": "object", "properties": { "list": { "type": "array", "items": { "$ref": "#/components/schemas/CertVO" } } } }] } } } ] } } } } }
      },
      "post": {
        "tags": ["guard.cert"],
        "summary": "上传证书",
        "description": "**Content-Type 为 application/json**（不是 multipart/form-data）。直接集成时 cert/key 是 PEM 文本；不要把私钥写入 AI 对话、工单、日志或可观测埋点。通过 Aegeon Cloud 对话助手操作时应优先使用安全证书附件。",
        "x-permissions": ["guard.cert.upload"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CertUploadReq" } } } },
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/CertVO" } } } ] } } } }, "400": { "description": "PEM 解析失败或 name 已存在" } }
      }
    },
    "/api/guard/certs/{id}": {
      "get": {
        "tags": ["guard.cert"],
        "summary": "查询证书详情（含 PEM 文本）",
        "x-permissions": ["guard.cert.view"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/CertDetailVO" } } } ] } } } }, "404": { "description": "证书不存在" } }
      },
      "put": {
        "tags": ["guard.cert"],
        "summary": "更新证书",
        "x-permissions": ["guard.cert.edit"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CertUpdateReq" } } } },
        "responses": { "200": { "description": "成功" }, "404": { "description": "证书不存在" } }
      },
      "delete": {
        "tags": ["guard.cert"],
        "summary": "删除证书",
        "x-permissions": ["guard.cert.delete"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }],
        "responses": { "200": { "description": "成功" }, "404": { "description": "证书不存在" } }
      }
    },
    "/api/guard/certs/{id}/domains": {
      "get": {
        "tags": ["guard.cert"],
        "summary": "查询证书绑定的域名列表",
        "description": "返回 cert_domain_ships 关联记录（domain_id/domain/cert_id/ctime）。复用 view 权限。",
        "x-permissions": ["guard.cert.view"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/CertDomainVO" } } } } ] } } } }, "404": { "description": "证书不存在" } }
      }
    },
    "/api/guard/certs/{id}/bind": {
      "post": {
        "tags": ["guard.cert"],
        "summary": "绑定证书到域名",
        "x-permissions": ["guard.cert.bind"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CertBindReq" } } } },
        "responses": { "200": { "description": "成功" }, "404": { "description": "证书或域名不存在" }, "409": { "description": "证书与域名已绑定" } }
      }
    },
    "/api/guard/certs/{id}/bind/{domainId}": {
      "delete": {
        "tags": ["guard.cert"],
        "summary": "解绑证书与域名",
        "x-permissions": ["guard.cert.bind"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } },
          { "name": "domainId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "成功" } }
      }
    },
    "/api/guard/bwlist/sets": {
      "get": {
        "tags": ["guard.bwlist"],
        "summary": "分页查询黑白名单集合列表",
        "x-permissions": ["guard.bwlist.list"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "size", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } },
          { "name": "keyword", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "allOf": [{ "$ref": "#/components/schemas/PageData" }, { "type": "object", "properties": { "list": { "type": "array", "items": { "$ref": "#/components/schemas/IPSetVO" } } } }] } } } ] } } } } }
      },
      "post": {
        "tags": ["guard.bwlist"],
        "summary": "创建黑/白名单集合",
        "x-permissions": ["guard.bwlist.create"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BWListSetCreateReq" } } } },
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/IPSetVO" } } } ] } } } }, "400": { "description": "name 已存在或 ip_set_type 非法" } }
      }
    },
    "/api/guard/bwlist/sets/{id}": {
      "put": {
        "tags": ["guard.bwlist"],
        "summary": "编辑黑/白名单集合",
        "x-permissions": ["guard.bwlist.edit"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BWListSetUpdateReq" } } } },
        "responses": { "200": { "description": "成功" } }
      },
      "delete": {
        "tags": ["guard.bwlist"],
        "summary": "删除黑/白名单集合",
        "description": "级联删除集合下所有 IP 条目。",
        "x-permissions": ["guard.bwlist.delete"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }],
        "responses": { "200": { "description": "成功" } }
      }
    },
    "/api/guard/bwlist/sets/{id}/ips": {
      "get": {
        "tags": ["guard.bwlist"],
        "summary": "查询集合下 IP 列表",
        "x-permissions": ["guard.bwlist.ip_list"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } },
          { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "size", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } }
        ],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "allOf": [{ "$ref": "#/components/schemas/PageData" }, { "type": "object", "properties": { "list": { "type": "array", "items": { "$ref": "#/components/schemas/IPVO" } } } }] } } } ] } } } } }
      },
      "post": {
        "tags": ["guard.bwlist"],
        "summary": "添加单条 IP 到集合",
        "x-permissions": ["guard.bwlist.ip_add"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BWListIPAddReq" } } } },
        "responses": { "200": { "description": "成功" }, "409": { "description": "IP 已存在" } }
      }
    },
    "/api/guard/bwlist/sets/{id}/ips/batch": {
      "post": {
        "tags": ["guard.bwlist"],
        "summary": "批量添加 IP 到集合",
        "x-permissions": ["guard.bwlist.ip_add"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BWListIPBatchAddReq" } } } },
        "responses": { "200": { "description": "成功" } }
      }
    },
    "/api/guard/bwlist/sets/{id}/ips/batch/delete": {
      "post": {
        "tags": ["guard.bwlist"],
        "summary": "批量删除集合下 IP",
        "description": "按 IP 条目 ID 在指定集合内批量删除，一次事务原子完成；删除范围以集合 {id} 限定，传入不属于该集合的 ID 会被忽略，无任一命中返回 404。",
        "x-permissions": ["guard.bwlist.ip_delete"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BWListIPBatchDeleteReq" } } } },
        "responses": { "200": { "description": "成功，data.deleted 为实际删除条数" }, "404": { "description": "集合内无匹配的 IP" } }
      }
    },
    "/api/guard/bwlist/ips/{id}": {
      "get": {
        "tags": ["guard.bwlist"],
        "summary": "查询单条 IP 详情",
        "x-permissions": ["guard.bwlist.ip_list"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/IPVO" } } } ] } } } }, "404": { "description": "IP 不存在" } }
      },
      "delete": {
        "tags": ["guard.bwlist"],
        "summary": "删除单条 IP",
        "x-permissions": ["guard.bwlist.ip_delete"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }],
        "responses": { "200": { "description": "成功" } }
      }
    },
    "/api/guard/forwards": {
      "get": {
        "tags": ["guard.forward"],
        "summary": "分页查询 IP 转发规则列表",
        "x-permissions": ["guard.forward.list"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "size", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } },
          { "name": "user_id", "in": "query", "schema": { "type": "string" } },
          { "name": "domain_id", "in": "query", "schema": { "type": "string" } },
          { "name": "status", "in": "query", "schema": { "type": "integer", "format": "int32", "enum": [1, 2] } },
          { "name": "keyword", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "allOf": [{ "$ref": "#/components/schemas/PageData" }, { "type": "object", "properties": { "list": { "type": "array", "items": { "$ref": "#/components/schemas/ForwardVO" } } } }] } } } ] } } } } }
      },
      "post": {
        "tags": ["guard.forward"],
        "summary": "创建 IP 转发规则（TCP/UDP 端口转发）",
        "x-permissions": ["guard.forward.create"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ForwardCreateReq" } } } },
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/ForwardVO" } } } ] } } } } }
      }
    },
    "/api/guard/forwards/{id}": {
      "get": {
        "tags": ["guard.forward"],
        "summary": "查询单条 IP 转发规则详情",
        "x-permissions": ["guard.forward.view"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/ForwardVO" } } } ] } } } }, "404": { "description": "规则不存在" } }
      },
      "put": {
        "tags": ["guard.forward"],
        "summary": "编辑 IP 转发规则",
        "x-permissions": ["guard.forward.edit"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ForwardUpdateReq" } } } },
        "responses": { "200": { "description": "成功" }, "404": { "description": "规则不存在" } }
      },
      "delete": {
        "tags": ["guard.forward"],
        "summary": "删除 IP 转发规则",
        "x-permissions": ["guard.forward.delete"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }],
        "responses": { "200": { "description": "成功" }, "404": { "description": "规则不存在" } }
      }
    },
    "/api/guard/schedules/domains": {
      "get": {
        "tags": ["guard.schedule"],
        "summary": "List DNS schedule domains",
        "description": "分页查询参与 DNS 调度的域名列表（含每个域名的 SRC/NODE 解析条数与最近一条事务状态）。\n\nList domains under DNS scheduling with SRC/NODE record counts and the latest affair status. Backend reads `dns_records.group_type` (zdns_db, source of truth) and joins `guard_configs.parsing_state` (guard_db); when out of sync the frontend should render a \"syncing\" badge.",
        "x-permissions": ["guard.schedule.list"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "page",    "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "size",    "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } },
          { "name": "keyword", "in": "query", "schema": { "type": "string" } },
          { "name": "user_id", "in": "query", "schema": { "type": "string" } },
          { "name": "mode",    "in": "query", "schema": { "type": "integer", "format": "int32", "enum": [0, 1, 2] }, "description": "0=all 1=SRC 2=NODE" },
          { "name": "status",  "in": "query", "schema": { "type": "integer", "format": "int32" } }
        ],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/ScheduleDomainListResp" } } } ] } } } } }
      }
    },
    "/api/guard/schedules/domains/{id}/switch-mode": {
      "post": {
        "tags": ["guard.schedule"],
        "summary": "Switch SRC/NODE mode for a domain",
        "description": "切换该域名 DNS 解析模式（SRC 源站 / NODE 节点）。在 zdns_db 单库事务内 `SELECT FOR UPDATE` 锁住 dns_records → 更新 group_type/switch_state → 落 dns_affairs（status=Start）；事务提交后通过 NSQ topic=`dns` 异步通知 zdns 服务生效；前端必须轮询 affairs 接口获取最终状态。\n\nSwitch the DNS parsing mode (SRC or NODE) for this domain. Within a zdns_db transaction, dns_records are locked with `SELECT FOR UPDATE`, `group_type` and `switch_state` updated, and a `dns_affairs` row inserted as `AffairsStatus_Start`. After commit, an NSQ message on topic `dns` notifies the legacy zdns service to apply the change. The endpoint does **NOT** directly invoke any third-party DNS API — the frontend must poll the affairs endpoint to read the final status (`Succeed` / `Faild`).",
        "x-permissions": ["guard.schedule.switch"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "domain_id" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScheduleSwitchModeReq" } } } },
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/ScheduleAffairVO" } } } ] } } } }, "404": { "description": "域名不存在或无解析记录" } }
      }
    },
    "/api/guard/schedules/domains/{id}/init": {
      "post": {
        "tags": ["guard.schedule"],
        "summary": "Initialize DNS parsing for a domain",
        "description": "初始化该域名 DNS 解析：读取 guard_domain_settings 的源站/调度配置与 domain_node_ships 节点绑定，锁住该域名旧 dns_records 后删除并重建源站/节点记录，再落 dns_affairs 事务并通过 NSQ topic=`dns` 异步通知 zdns 同步一次。前端必须轮询 affairs 接口获取最终状态。\n\nInitialize DNS parsing for this domain. The backend reads source/dispatch settings from guard_domain_settings and node bindings from domain_node_ships, locks existing dns_records, deletes and recreates source/node records, then inserts a dns_affairs row and publishes NSQ topic=`dns` with Cmd=0 so the legacy zdns service syncs once. The frontend must poll the affairs endpoint for the final outcome.",
        "x-permissions": ["guard.schedule.init"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "domain_id" }],
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScheduleInitReq" } } } },
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/ScheduleAffairVO" } } } ] } } } }, "404": { "description": "域名不存在或无解析记录" } }
      }
    },
    "/api/guard/schedules/domains/{id}/reset": {
      "post": {
        "tags": ["guard.schedule"],
        "summary": "Reset DNS parsing for a domain",
        "description": "重置该域名所有 DNS 解析记录：`switch_state` 归位为 1，`status` 回滚到 `last_status`；落 dns_affairs 事务并通过 NSQ topic=`dns` 异步通知 zdns 服务生效。前端必须轮询 affairs 接口获取最终状态。\n\nReset all DNS records under this domain: `switch_state` returns to 1 and `status` rolls back to `last_status`. A `dns_affairs` row is inserted and an NSQ message published to topic `dns` for the legacy zdns service to apply asynchronously. The frontend must poll the affairs endpoint for the final outcome.",
        "x-permissions": ["guard.schedule.reset"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "domain_id" }],
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScheduleResetReq" } } } },
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/ScheduleAffairVO" } } } ] } } } } }
      }
    },
    "/api/guard/schedules/domains/{id}/records": {
      "get": {
        "tags": ["guard.schedule"],
        "summary": "List DNS records of a domain",
        "description": "分页查询该域名下所有 DNS 解析记录（直查 zdns_db.dns_records，只读）。\n\nList all DNS records under the domain (read-only on zdns_db.dns_records).",
        "x-permissions": ["guard.schedule.records"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id",         "in": "path",  "required": true, "schema": { "type": "string" }, "description": "domain_id" },
          { "name": "group_type", "in": "query", "schema": { "type": "integer", "format": "int32", "enum": [1, 2] }, "description": "1=SRC 2=NODE" },
          { "name": "status",     "in": "query", "schema": { "type": "integer", "format": "int32", "enum": [1, 2] }, "description": "1=disabled 2=enabled" },
          { "name": "page",       "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "size",       "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } }
        ],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/ScheduleRecordsResp" } } } ] } } } } }
      }
    },
    "/api/guard/schedules/records/batch-status": {
      "post": {
        "tags": ["guard.schedule"],
        "summary": "Batch enable/disable DNS records",
        "description": "批量启停 DNS 解析记录：`UPDATE dns_records SET last_status=status, status=? WHERE record_id IN (?)`；落 dns_affairs 事务并通过 NSQ topic=`dns` 异步通知。前端必须轮询 affairs 接口获取最终状态。\n\nBatch enable or disable DNS records. The endpoint persists the new status in zdns_db and publishes an NSQ message to topic `dns`; the frontend must poll the affairs endpoint for the final result.",
        "x-permissions": ["guard.schedule.batch"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScheduleBatchStatusReq" } } } },
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/ScheduleAffairVO" } } } ] } } } } }
      }
    },
    "/api/guard/schedules/affairs": {
      "get": {
        "tags": ["guard.schedule"],
        "summary": "List DNS schedule affair records",
        "description": "分页查询 DNS 调度事务记录（zdns_db.dns_affairs）。`domain_id` 过滤通过 `content LIKE` 实现。前端轮询此接口获取最终状态。\n\nList DNS schedule affair records on zdns_db.dns_affairs. The `domain_id` filter is implemented via `content LIKE`. Frontends should poll this endpoint to obtain the final outcome of asynchronous operations.",
        "x-permissions": ["guard.schedule.affairs"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "page",       "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "size",       "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } },
          { "name": "user_id",    "in": "query", "schema": { "type": "string" } },
          { "name": "status",     "in": "query", "schema": { "type": "string", "enum": ["AffairsStatus_Start", "AffairsStatus_Succeed", "AffairsStatus_Faild"] } },
          { "name": "ctime_from", "in": "query", "schema": { "type": "integer", "format": "int64" } },
          { "name": "ctime_to",   "in": "query", "schema": { "type": "integer", "format": "int64" } },
          { "name": "domain_id",  "in": "query", "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/ScheduleAffairListResp" } } } ] } } } } }
      }
    },
    "/api/guard/schedules/affairs/{id}": {
      "get": {
        "tags": ["guard.schedule"],
        "summary": "Get a DNS schedule affair record",
        "description": "查询单条事务详情，含完整 message（HTML 片段）与 json_content（outbox 重试计数等扩展字段）。\n\nFetch a single affair record including its full `message` (HTML snippet) and `json_content` extension fields (outbox retry counters, etc.).",
        "x-permissions": ["guard.schedule.affairs"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "affairs_id" }],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/ScheduleAffairVO" } } } ] } } } }, "404": { "description": "事务记录不存在" } }
      }
    },
    "/api/guard/waf/rules": {
      "get": {
        "tags": ["guard.waf"],
        "summary": "分页查询 WAF 规则组列表",
        "description": "返回当前用户可见的全部 WAF 规则组（可按 policy_id 过滤）。`tag` 字段是规则组主键，路径 `{id}` 取此值；`rule_id` 仅为业务编号。",
        "x-permissions": ["guard.waf.list"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "page",      "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "size",      "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } },
          { "name": "policy_id", "in": "query", "schema": { "type": "string" }, "description": "按策略过滤" }
        ],
        "responses": {
          "200": {
            "description": "成功",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "allOf": [
                            { "$ref": "#/components/schemas/PageData" },
                            { "type": "object", "properties": { "list": { "type": "array", "items": { "$ref": "#/components/schemas/WafGroupVO" } } } }
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": ["guard.waf"],
        "summary": "创建 WAF 规则组",
        "description": "在指定策略下创建规则组。`name` 与 `policy_id` 必填；`rules[]` 可空，后续也可通过 PUT 接口全量替换。",
        "x-permissions": ["guard.waf.create"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WafGroupCreateReq" } } }
        },
        "responses": {
          "200": {
            "description": "成功",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/WafGroupVO" } } }
                  ]
                }
              }
            }
          },
          "400": { "description": "参数校验失败（name / policy_id 缺失等）" }
        }
      }
    },
    "/api/guard/waf/rules/{id}": {
      "put": {
        "tags": ["guard.waf"],
        "summary": "更新 WAF 规则组",
        "description": "按字段增量更新；`rules[]` 是全量替换（非增量）。启停切换不走本接口——调用 `PUT /api/guard/waf/rules/{id}/status`。路径 `{id}` 是规则组 `tag`（uint64 主键），不是 `rule_id`。",
        "x-permissions": ["guard.waf.edit"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" }, "description": "规则组 tag（uint64）" }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WafGroupUpdateReq" } } }
        },
        "responses": {
          "200": {
            "description": "成功",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/WafGroupVO" } } }
                  ]
                }
              }
            }
          },
          "404": { "description": "规则组不存在" }
        }
      },
      "delete": {
        "tags": ["guard.waf"],
        "summary": "删除 WAF 规则组",
        "description": "级联删除该规则组下的全部子规则（waf_rules.group_id = tag）。",
        "x-permissions": ["guard.waf.delete"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" }, "description": "规则组 tag（uint64）" }
        ],
        "responses": {
          "200": { "description": "成功" },
          "404": { "description": "规则组不存在" }
        }
      }
    },
    "/api/guard/waf/rules/{id}/status": {
      "put": {
        "tags": ["guard.waf"],
        "summary": "切换 WAF 规则组启停状态",
        "description": "独立于普通 update：本接口只接受 `status` 字段，且 `oneof=1 2` 强校验。S-6 修复后统一约定 1=禁用 2=启用（对齐 forwards/bwlist/schedules）。",
        "x-permissions": ["guard.waf.status"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" }, "description": "规则组 tag（uint64）" }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WafStatusReq" } } }
        },
        "responses": {
          "200": { "description": "成功" },
          "400": { "description": "status 必须为 1 或 2" },
          "404": { "description": "规则组不存在" }
        }
      }
    },
    "/api/plan/plans": {
      "get": {
        "tags": ["plan"],
        "summary": "套餐目录列表（分页）",
        "description": "查询套餐目录，支持按产品类型（prod_type）和关键词（keyword）过滤，分页返回。所需权限：`plan.plan.list`。\n\n**注意**：套餐的创建/编辑/删除、为用户开通、订阅查询属平台控制台管理操作，直接操作在线计费数据，不在对外对接 API/CLI 范围（仅平台运维经控制台 + RBAC 使用）。",
        "x-permissions": ["plan.plan.list"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "page",      "in": "query", "required": false, "schema": { "type": "integer", "default": 1  }, "description": "页码（从 1 开始）" },
          { "name": "size",      "in": "query", "required": false, "schema": { "type": "integer", "default": 20 }, "description": "每页条数（默认 20）" },
          { "name": "prod_type", "in": "query", "required": false, "schema": { "type": "integer" }, "description": "产品类型过滤（2=WAF 4=Monitor 32=GFIP）；缺省返回全部" },
          { "name": "keyword",   "in": "query", "required": false, "schema": { "type": "string"  }, "description": "套餐名称关键词模糊搜索" }
        ],
        "responses": {
          "200": {
            "description": "成功",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "list":  { "type": "array", "items": { "$ref": "#/components/schemas/PlanVO" } },
                            "total": { "type": "integer" },
                            "page":  { "type": "integer" },
                            "size":  { "type": "integer" }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": { "description": "未鉴权" },
          "403": { "description": "权限不足（需 plan.plan.list）" }
        }
      }
    },
    "/api/plan/plans/{id}": {
      "get": {
        "tags": ["plan"],
        "summary": "套餐详情",
        "description": "按套餐 ID 查询单个套餐的完整信息（含 content 配额 JSON）。所需权限：`plan.plan.view`。\n\n**注意**：套餐的创建/编辑/删除、为用户开通、订阅查询属平台控制台管理操作，直接操作在线计费数据，不在对外对接 API/CLI 范围（仅平台运维经控制台 + RBAC 使用）。",
        "x-permissions": ["plan.plan.view"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "套餐 ID（UUID）" }
        ],
        "responses": {
          "200": {
            "description": "成功",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/PlanVO" } } }
                  ]
                }
              }
            }
          },
          "401": { "description": "未鉴权" },
          "403": { "description": "权限不足（需 plan.plan.view）" },
          "404": { "description": "套餐不存在" }
        }
      }
    },
    "/api/analytics/alert/{id}": {
      "get": {
        "tags": ["analytics"],
        "summary": "查询单条告警详情",
        "description": "返回字段含处置元信息（process_uid/process_time/last_update_timestamp）。非超管会按 user_id 强制过滤。",
        "x-permissions": ["analytics.alert.view"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/AlertVO" } } } ] } } } }, "404": { "description": "告警不存在或无权访问" } }
      }
    },
    "/api/node/install/script": {
      "get": {
        "tags": ["node.install"],
        "summary": "获取节点安装脚本",
        "description": "返回可直接执行的 bash 安装脚本。校验 token 有效性但不消耗 max_uses（使用 ValidateBearerNoUse）。脚本包含 SHA256 校验、bounded curl 超时、set -euo pipefail 等安全加固。",
        "x-permissions": ["node.node.install"],
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" }, "description": "Bearer token（从创建 token 响应获取）" }
        ],
        "responses": {
          "200": {
            "description": "安装脚本内容（text/plain）",
            "content": { "text/plain": { "schema": { "type": "string" } } }
          },
          "401": {
            "description": "Token 无效或已过期",
            "headers": { "WWW-Authenticate": { "schema": { "type": "string" }, "description": "Bearer realm=\"node-install\"" } }
          }
        }
      }
    },
    "/api/node/install/package": {
      "post": {
        "tags": ["node.install"],
        "summary": "获取安装包信息",
        "description": "返回安装包下载地址和元信息。每次调用消耗 use_count（使用 ValidateBearer），use_count 达到 max_uses 后 token 失效。",
        "x-permissions": ["node.node.install"],
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" }, "description": "Bearer token" }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/PackageRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "安装包信息",
            "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/PackageResponse" } } } ] } } }
          },
          "401": {
            "description": "Token 无效、已过期或 use_count 已耗尽",
            "headers": { "WWW-Authenticate": { "schema": { "type": "string" }, "description": "Bearer realm=\"node-install\"" } }
          }
        }
      }
    },
    "/api/node/install/env": {
      "post": {
        "tags": ["node.install"],
        "summary": "获取环境配置",
        "description": "返回节点运行所需的环境变量和配置。每次调用消耗 use_count（使用 ValidateBearer）。环境变量通过显式 allow-list 过滤，仅返回允许的键。",
        "x-permissions": ["node.node.install"],
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" }, "description": "Bearer token" }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/EnvRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "环境配置",
            "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/EnvResponse" } } } ] } } }
          },
          "401": {
            "description": "Token 无效、已过期或 use_count 已耗尽",
            "headers": { "WWW-Authenticate": { "schema": { "type": "string" }, "description": "Bearer realm=\"node-install\"" } }
          }
        }
      }
    },
    "/api/node/reg": {
      "post": {
        "tags": ["node.install"],
        "summary": "节点 agent 自注册",
        "description": "节点 agent 自注册端点。公共端点：不挂 RBAC、不需要 Bearer 鉴权，与 /api/node/install/report 同类（agent 基础设施）。唯一访问门槛是请求体里的共享口令 dummy_token——必须等于 agent 硬编码的固定常量值。\n\n语义：按管理地址（manger_addr）幂等 upsert。同一管理地址重复注册返回相同 node_id，绝不新建重复节点，也不覆盖既有节点字段（新老 agent 共存时老节点记录原样保留）。首次注册时创建一条节点记录（machine_room 默认值为 \"default\"），并以管理 IP 派生落一条 ip_addr 记录——于是节点自动出现在节点列表，且管理地址与业务 IP 都已自动填好。落 ip_addr 为尽力而为：IP 已被占用等非致命错误不影响注册成功。\n\n失败处理：始终返回 HTTP 200，业务错误通过响应体非零 code 表达。dummy_token 不匹配、manger_addr 缺失或非法、node_type 非 WAF 类型均返回非零 code 并附带错误消息。",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/RegNodeRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "注册成功返回 code=0 与 RegNodeResponse；业务失败（无效 dummy_token / 无效 manger_addr / 非 WAF 类型）返回非零 code",
            "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/RegNodeResponse" } } } ] } } }
          }
        }
      }
    },
    "/api/node/install/report": {
      "post": {
        "tags": ["node.install"],
        "summary": "上报安装/升级结果",
        "description": "节点安装/升级完成后上报结果。使用 report_count 独立计数（不消耗 max_uses）。失败报告保持 token active，成功报告将 token 标记为 used。不会覆盖已达到终态的 job 状态。",
        "x-permissions": ["node.node.install"],
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" }, "description": "Bearer token" }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ReportRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "上报成功",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } }
          },
          "401": {
            "description": "Token 无效（已 revoked 的 token 仅允许 failed 状态的 report）",
            "headers": { "WWW-Authenticate": { "schema": { "type": "string" }, "description": "Bearer realm=\"node-install\"" } }
          }
        }
      }
    },
    "/api/node/install/artifacts": {
      "get": {
        "tags": ["node.install"],
        "summary": "列出安装包制品",
        "description": "返回可用的安装包制品列表。制品路径由 NODE_INSTALL_ARTIFACT_ROOTS 环境变量控制，默认包含 /data/artifacts 和 /opt/cloud/artifacts（不含 /tmp）。响应不包含 package_path 字段。",
        "x-permissions": ["node.node.artifact"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "responses": {
          "200": {
            "description": "制品列表",
            "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/ArtifactVO" } } } } ] } } }
          }
        }
      }
    },
    "/api/node/install/artifacts/upload": {
      "post": {
        "tags": ["node.install"],
        "summary": "上传节点安装包",
        "description": "直接从浏览器上传安装包(multipart/form-data),后端流式落盘到 NODE_INSTALL_ARTIFACT_ROOTS 第一项(默认 /data/artifacts)后,复用注册逻辑做 scan + 预检 + 落库。无需先把包放到服务器。单文件最大 1GB。",
        "x-permissions": ["node.node.artifact"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["package"],
                "properties": {
                  "package": { "type": "string", "format": "binary", "description": "安装包文件(.tar.gz / .tgz)" },
                  "name": { "type": "string", "description": "安装包名称,可选,留空用上传文件名" },
                  "version": { "type": "string", "description": "版本,可选,留空由后端从 VERSIONS 推断" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "上传并注册成功",
            "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/ArtifactVO" } } } ] } } }
          }
        }
      }
    },
    "/api/node/install/commands": {
      "get": {
        "tags": ["node.install"],
        "summary": "列出安装命令",
        "description": "返回已创建的安装命令列表。命令内容仅在创建时展示一次，后续查询只返回 token_id 和 hash/prefix。",
        "x-permissions": ["node.node.install"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "responses": {
          "200": {
            "description": "命令列表",
            "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/CommandVO" } } } } ] } } }
          }
        }
      }
    },
    "/api/node/install/upgrades": {
      "get": {
        "tags": ["node.install"],
        "summary": "列出升级命令",
        "description": "返回已创建的升级命令列表。",
        "x-permissions": ["node.node.upgrade"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "responses": {
          "200": {
            "description": "升级命令列表",
            "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/CommandVO" } } } } ] } } }
          }
        }
      }
    },
    "/api/node/install/uninstalls": {
      "post": {
        "tags": ["node.install"],
        "summary": "生成节点卸载命令",
        "description": "生成一次性卸载命令（等价于 action=uninstall），返回 curl … | sudo bash 一行命令；明文 token 仅展示一次。与升级一样作用于已有节点，**node_id 必填**（缺省 → 400「卸载必须指定目标节点」）。请求体同 install/upgrade。引导脚本据 GET /api/node/install/package 返回的服务端权威响应头 X-Install-Action=uninstall（取自 token 绑定 job 的动作）决定跑包内 uninstall.sh 而非 install.sh，并自动应答其交互式 [y/N] 确认。**破坏性、不可恢复**：uninstall.sh 会停止并移除该节点上的全部防护服务（nginx / agent / waf-spoa 等）及其数据目录。**范围**：卸载只移除节点主机上的服务，不删除云端节点列表里的节点记录（如需删记录，运维另行调用 DELETE /api/node/nodes/:id）。",
        "x-permissions": ["node.node.uninstall"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CommandRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "卸载命令生成成功",
            "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/CommandVO" } } } ] } } }
          },
          "400": { "description": "node_id 缺失 / server_url 不匹配 / artifact 未就绪 / env 校验失败" }
        }
      }
    },
    "/api/node/install/jobs": {
      "get": {
        "tags": ["node.install"],
        "summary": "列出安装/升级任务",
        "description": "返回任务列表，支持分页和状态过滤。任务状态：pending → running → success/failed（终态）。Reaper 每 10 分钟运行一次，将超过 24 小时的 pending/running 任务标记为 failed 并 revoke 关联的 active token。",
        "x-permissions": ["node.node.job"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["pending", "running", "success", "failed"] }, "description": "按状态过滤" },
          { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "size", "in": "query", "schema": { "type": "integer", "default": 20 } }
        ],
        "responses": {
          "200": {
            "description": "任务列表",
            "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/JobVO" } }, "total": { "type": "integer" } } } } } ] } } }
          }
        }
      }
    },
    "/api/node/install/jobs/{id}": {
      "get": {
        "tags": ["node.install"],
        "summary": "查询任务详情",
        "description": "返回单个任务的详细信息，包括环境变量的 sha256 和键名列表（不返回明文 env）。",
        "x-permissions": ["node.node.job"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": {
            "description": "任务详情",
            "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/JobVO" } } } ] } } }
          },
          "404": { "description": "任务不存在" }
        }
      }
    },
    "/api/node/install/tokens": {
      "post": {
        "tags": ["node.install"],
        "summary": "创建安装 token",
        "description": "创建新的安装 token。token 明文仅在创建时返回一次，后续只存储 hash 和 prefix。默认 max_uses=20，最小值 5，最大值 100。",
        "x-permissions": ["node.node.install"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreateTokenRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token 创建成功",
            "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/TokenVO" } } } ] } } }
          }
        }
      },
      "get": {
        "tags": ["node.install"],
        "summary": "列出安装 token",
        "description": "返回 token 列表，包含状态（active/used/revoked）、use_count、report_count 等信息。不返回 token 明文。",
        "x-permissions": ["node.node.install"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "responses": {
          "200": {
            "description": "Token 列表",
            "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/Envelope" }, { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/TokenListVO" } } } } ] } } }
          }
        }
      }
    },
    "/api/node/install/tokens/{id}/revoke": {
      "post": {
        "tags": ["node.install"],
        "summary": "吊销安装 token",
        "description": "将指定 token 标记为 revoked。已 revoked 的 token 不能再用于 /script、/package、/env 调用，但允许上报 failed 状态的 report。",
        "x-permissions": ["node.node.revoke"],
        "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": {
            "description": "吊销成功",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } }
          },
          "404": { "description": "Token 不存在" }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Envelope": {
        "type": "object",
        "properties": {
          "code": { "type": "integer" },
          "message": { "type": "string" },
          "data": {}
        }
      },
      "PackageRequest": {
        "type": "object",
        "properties": {
          "arch": { "type": "string", "description": "CPU 架构（amd64/arm64）" },
          "os": { "type": "string", "description": "操作系统（linux）" }
        }
      },
      "PackageResponse": {
        "type": "object",
        "properties": {
          "url": { "type": "string", "description": "安装包下载 URL" },
          "sha256": { "type": "string", "description": "安装包 SHA256 校验和" },
          "filename": { "type": "string", "description": "文件名" },
          "size": { "type": "integer", "description": "文件大小（字节）" }
        }
      },
      "EnvRequest": {
        "type": "object",
        "properties": {
          "node_id": { "type": "string", "description": "节点 ID（可选）" }
        }
      },
      "EnvResponse": {
        "type": "object",
        "properties": {
          "env": { "type": "object", "additionalProperties": { "type": "string" }, "description": "环境变量键值对（仅返回 allow-list 中的键）" },
          "sha256": { "type": "string", "description": "环境变量内容的 SHA256" }
        }
      },
      "ReportRequest": {
        "type": "object",
        "required": ["status"],
        "properties": {
          "status": { "type": "string", "enum": ["success", "failed"], "description": "上报状态" },
          "message": { "type": "string", "description": "附加消息（错误信息等）" },
          "node_id": { "type": "string", "description": "节点 ID" }
        }
      },
      "RegNodeRequest": {
        "type": "object",
        "required": ["manger_addr", "dummy_token"],
        "properties": {
          "node_id": { "type": "string", "description": "节点 ID。首次安装为空；非空表示 agent 已持有节点 ID（用于复用既有节点，不新建）" },
          "manger_addr": { "type": "string", "description": "管理地址 host:port，agent 用 ip route get 自动探测后上报（必填）。注意字段名为 agent 既有拼写 manger_addr（缺 'a'）。端口缺失时回落默认端口 33020" },
          "node_type": { "type": "string", "description": "proto 枚举名字符串，如 \"NODE_1_WAF\"。仅支持 WAF 防护节点（空 / \"NODE_1_WAF\" / \"waf\" / \"1\" 均映射为 WAF，其它值返回非零 code）" },
          "extend_config": { "type": "string", "description": "url-escape 后的扩展配置，自注册暂不消费" },
          "dummy_token": { "type": "string", "description": "共享口令（必填），必须等于 agent 硬编码的固定常量值，否则返回非零 code 拒绝。公共端点的唯一访问门槛" },
          "plugin": { "type": "string", "description": "插件列表，如 \"waf,detect,agent,ebpf\"" },
          "only_acl": { "type": "boolean", "description": "仅 ACL 模式标记，自注册节点不走该路径" },
          "ip": { "type": "string", "description": "only_acl 关联参数，标准注册为空" },
          "acl_tags": { "type": "string", "description": "only_acl 关联参数" },
          "ip_groups": { "type": "string", "description": "only_acl 关联参数" }
        }
      },
      "RegNodeResponse": {
        "type": "object",
        "properties": {
          "node_id": { "type": "string", "description": "节点 ID。幂等：同一管理地址重复注册返回既有 node_id；首次注册返回新建的 node_id" },
          "listen_addr": { "type": "string", "description": "监听地址，形如 \":33020\"，取自管理地址端口（缺失回落默认 33020）" },
          "tls": { "type": "boolean", "description": "是否启用 TLS。新平台（NSQ）固定返回 false（不再用 etcd 下发每节点证书）" },
          "cert": { "type": "string", "description": "证书，新平台返回空串" },
          "key": { "type": "string", "description": "私钥，新平台返回空串" },
          "settings": { "type": "object", "additionalProperties": { "type": "string" }, "description": "下发配置。新平台配置走 NSQ 发布订阅，固定返回空映射" },
          "plugin": { "type": "object", "additionalProperties": { "type": "object", "additionalProperties": { "type": "string" } }, "description": "插件配置。新平台固定返回空映射" }
        }
      },
      "ArtifactVO": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "name": { "type": "string", "description": "制品名称" },
          "version": { "type": "string", "description": "版本号" },
          "arch": { "type": "string", "description": "CPU 架构" },
          "os": { "type": "string", "description": "操作系统" },
          "size": { "type": "integer", "description": "文件大小" },
          "sha256": { "type": "string", "description": "SHA256 校验和" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "CommandRequest": {
        "type": "object",
        "required": ["artifact_id", "server_url"],
        "description": "生成安装/升级/卸载命令的请求体（install、upgrade、uninstall 共用）。",
        "properties": {
          "artifact_id": { "type": "string", "description": "安装包 artifact ID（必填）" },
          "node_id": { "type": "string", "description": "目标节点 ID。新装（commands）忽略此字段；升级（upgrades）与卸载（uninstalls）必填，缺省 → 400「卸载必须指定目标节点」" },
          "server_url": { "type": "string", "description": "cloud 对外 base URL（必填）。必须匹配 cloud 配置的对外 base URL（scheme + host[:port]）；localhost 之外强制 https" },
          "env": { "type": "object", "additionalProperties": { "type": "string" }, "description": "可选的节点变量覆盖（叠加在注册时设定于 artifact 的变量之上）；命中敏感词或疑似 token/Bearer 凭证的键 → 400" },
          "ttl_seconds": { "type": "integer", "description": "token 有效期秒数，默认 3600，上限 86400（24h）" },
          "max_uses": { "type": "integer", "description": "token 最大使用次数，最低 5、默认 20、上限 100" }
        }
      },
      "CommandVO": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "token_id": { "type": "integer" },
          "token_prefix": { "type": "string", "description": "Token 前缀（用于展示）" },
          "command_type": { "type": "string", "enum": ["install", "upgrade", "uninstall"] },
          "status": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "JobVO": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "token_id": { "type": "integer" },
          "command_type": { "type": "string", "enum": ["install", "upgrade"] },
          "status": { "type": "string", "enum": ["pending", "running", "success", "failed"] },
          "env_sha256": { "type": "string", "description": "环境变量 SHA256（不返回明文）" },
          "env_keys": { "type": "array", "items": { "type": "string" }, "description": "环境变量键名列表" },
          "message": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "CreateTokenRequest": {
        "type": "object",
        "properties": {
          "max_uses": { "type": "integer", "default": 20, "minimum": 5, "maximum": 100, "description": "最大使用次数（package/env 调用消耗）" },
          "expires_in": { "type": "integer", "description": "过期时间（秒）" },
          "comment": { "type": "string", "description": "备注" }
        }
      },
      "TokenVO": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "token": { "type": "string", "description": "Token 明文（仅创建时返回一次）" },
          "token_prefix": { "type": "string", "description": "Token 前缀" },
          "status": { "type": "string", "enum": ["active", "used", "revoked"] },
          "max_uses": { "type": "integer" },
          "use_count": { "type": "integer", "description": "已使用次数（package/env 消耗）" },
          "report_count": { "type": "integer", "description": "已上报次数（独立计数，不消耗 max_uses）" },
          "expires_at": { "type": "string", "format": "date-time" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "TokenListVO": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "token_prefix": { "type": "string" },
          "status": { "type": "string", "enum": ["active", "used", "revoked"] },
          "max_uses": { "type": "integer" },
          "use_count": { "type": "integer" },
          "report_count": { "type": "integer" },
          "expires_at": { "type": "string", "format": "date-time" },
          "created_at": { "type": "string", "format": "date-time" },
          "comment": { "type": "string" }
        }
      },
      "AlertVO": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "level": { "type": "string" },
          "domain": { "type": "string" },
          "type": { "type": "string" },
          "detail": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" },
          "ack_at": { "type": "string", "format": "date-time" },
          "ack_by": { "type": "string" }
        }
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      },
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      }
    }
  }
}
