Files
ai-gateway-go/docs/rewrite-progress.md
T
superidou 9501751792 0.10.1: 安全与业务逻辑加固、新品牌与部署加固
三轮审查修复(60+ 项),相对远端 main(b536672)的关键变更:
- 安全: 数据面 SSRF 拨号防护(防 DNS rebinding)/上游凭据剥离/登录防枚举
  与锁定态统一/可信代理(X-Forwarded-For)限流加固/会话版本失效机制/
  撤销即时传播/弱密钥拒绝启动/脱敏字节级重写(保签名契约)
- 业务逻辑: 裸 body 上传 panic/bootstrap 审计管线卡死/定价通配符优先级/
  全局工具可见性/调度器停机补跑/TOTP 挑战令牌消费顺序/熔断探针语义/
  >4MB 响应 token 计量/管理员重置密码作废会话 等
- 前端: 新 logo(语枢 AI 网关主题)/Provider 凭据异常警示/删除入口/
  后端错误消息透传/localStorage 敏感数据收敛
- 部署: CREDENTIAL_MASTER_KEY 持久化与弱值拒绝/Provider DELETE 接口/
  nginx 安全头/worker 内存限制
- 新增迁移 000029(key_hash 索引)/000030(usage_daily 币种维度)
2026-08-13 10:50:51 +08:00

186 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 重构进度
更新时间:2026-08-12
## 已完成:M0 工程基线
- Go 1.26 模块、配置校验、结构化日志和优雅退出。
- PostgreSQL `pgx` 连接池、critical/cache 双 Redis 客户端。
- 独立且带校验和、事务和 advisory lock 的数据库迁移器。
- OpenAI 兼容入口、bootstrap key 鉴权、请求体限制、连接池和 SSE 透传。
- `healthz`、依赖感知 `readyz`、基础 Prometheus 指标。
- PostgreSQL outbox、API Key、Provider 与分区审计表的首版 schema。
- 不包含 MinIO/S3、ClickHouse 的 Docker Compose 基线。
- Art Design Pro v3.0.2 管理端与门户端,已执行官方精简流程并通过生产构建。
- OpenAPI 运行时契约和共享 TypeScript 系统客户端起点。
## 已完成:M1 身份、Provider 与配置面
已完成:
- 管理员、门户用户 schema 和独立 bootstrap 命令。
- 兼容 Python 版本 60 万轮/旧 12 万轮 PBKDF2-SHA256,并在成功登录时升级旧哈希。
- critical Redis 可撤销不透明会话、数据库 active/role 复核、失败次数原子更新和账号锁定。
- `/api/v1/admin/*``/api/v1/portal/*` 登录、身份、菜单、退出契约。
- 管理端与门户端 TOTP 配置、二次登录、停用、备用码重置;含临时挑战令牌、时间步原子防重放和备用码一次性消费。
- 两套 Art 登录页已接入动态验证码/备用码,并通过生产构建。
- 数据库 API Key 创建、列表、scope、过期、Redis 共享缓存和即时撤销;明文只显示一次,创建/撤销与 outbox 同事务。
- Art 管理端 API Key 页面、动态菜单入口及生产构建。
- Provider AES-256-GCM 凭据、SSRF 地址校验、revision 和事务 outbox。
- Provider 数据库运行时原子快照、默认/显式路由、能力约束、共享连接池、周期刷新和上一有效版本回退。
- Art 管理端供应商页面及生产后端权限模式。
- Provider 连接测试、模型目录同步、历史模型停用策略和 `provider.models_synced` 事务 outbox。
- Art 管理端连接测试、同步结果和模型目录查看交互。
- 版本化 KEK keyring、历史密钥解密、Provider 凭据事务轮换和管理端轮换入口。
- PostgreSQL 权威 Provider 配置、critical Redis 多实例变更通知、进程内不可变快照与周期兜底刷新。
- 管理员/门户账号管理、内置角色、直接权限字符串、权限感知动态菜单和即时数据库复核。
- bootstrap API Key 显式关闭开关及兼容入口使用量指标。
- 层级部门 CRUD、循环检测、在用停用保护、门户账号部门绑定和部门 outbox。
- OIDC 配置面、加密 Client Secret、Authorization Code + PKCE、RS256/JWKS 校验、自动开户和一次性交换码。
- SAML 配置面、SP metadata、SP-initiated Redirect/POST 流程、SHA-256+ XML 签名断言校验、RelayState/InResponseTo 绑定、断言防重放、自动开户和部门绑定。
- 旧 Python 网关 38 个持久化实体字段字典、实体去向清单、确定性 UUIDv5 生成器和幂等旧 ID 映射表。
- SAML 与 OIDC 均已通过 PostgreSQL + Redis + 本地模拟 IdP 的端到端登录、自动开户、一次性交换和重放拦截测试。
## 已完成:M2 流量治理与路由
已完成:
- API Key 级每分钟请求上限和 UTC 自然月请求配额,`0` 明确表示不限制。
- critical Redis Lua 原子准入;限流返回 `429`/`Retry-After`/`X-RateLimit-*`,配置限制时 Redis 故障失败关闭。
- API Key 限额的 PostgreSQL 权威配置、鉴权缓存传递、Art 管理表单与 OpenAPI 契约。
- PostgreSQL + Redis + 模拟上游端到端测试:RPM=2 精确放行 2 次,月配额=3 精确放行 3 次。
- API Key UTC 自然月 Token 配额:请求前按输入体积与最大输出预算原子预留,响应结束按上游 usage 原子校准。
- OpenAI/Responses/Anthropic 的 JSON 与 SSE usage 增量提取;不缓冲完整流式响应,上游未返回 usage 时按预留预算保守计量。
- Art 管理端展示当月 Token 用量并可即时调整 RPM、月请求和月 Token 配额;更新同时清除共享鉴权缓存并写入 outbox。
- Provider 级响应头超时、有限指数退避和熔断;仅 GET/HEAD 或携带 `Idempotency-Key` 的可重放请求允许重试,所有阈值均可通过环境变量配置。
- PostgreSQL 模型别名/路由规则、事务 outbox、管理 CRUD、Redis 多实例变更通知和进程内不可变路由快照。
- 按 endpoint、API Key、tenant 条件匹配;最高优先级分组内使用请求 ID 做确定性加权选择,显式 Provider 作为候选约束。
- 已登记的模型别名在条件不匹配、规则停用或显式 Provider 不属于候选集时失败关闭,避免把别名透传到默认上游绕过策略。
- 代理在命中规则时改写上游模型并返回 `X-Gateway-Model`;未配置规则时不读取请求体,保持原代理热路径性能。
- Art 管理端模型路由页面,可维护别名、上游模型、Provider、权重、优先级、条件和启停状态。
## 已完成:M3 审计、usage、内容策略与成本
已完成:
- 网关调用的有界异步审计队列、批量 PostgreSQL COPY、失败保留/重试、队列满降级和优雅停机排空。
- 请求 ID、API Key、tenant、Provider、原始/目标模型、协议、HTTP 状态、延迟及输入/输出 Token 采集。
- PostgreSQL 按日 API Key/Provider/模型 usage 聚合,不在请求热路径同步写数据库。
- `audit:read`/`usage:read` 权限、筛选查询 API、Art 管理端审计与用量页面。
- 审计 accepted/dropped/written/flush failures Prometheus 指标。
- 独立 `gateway-outbox-worker`,使用 PostgreSQL `FOR UPDATE SKIP LOCKED` 租约支持多实例并行抢占。
- Redis Lua 原子事件 ID 去重与 Stream 写入,数据库确认丢失后重投不会产生重复 Stream 消息。
- 指数退避、最大尝试次数、死信状态、`outbox:read`/`outbox:manage` 权限、管理 API 与 Art 人工重试页面。
- `event_consumptions` 与业务 handler 共用 PostgreSQL 事务的消费者幂等入口,handler 回滚时消费标记同步回滚。
- 独立 `gateway-maintenance` 周期任务,按 UTC 月预创建审计分区,事务迁移 default 分区数据,并按配置直接丢弃完整过期分区、精确清理边界月和长期 usage。
- 审计维护使用 PostgreSQL advisory lock 与表级事务锁,多副本启动时仍只有一个实例执行;重复运行保持幂等。
- PostgreSQL 内容策略 CRUD、事务 outbox、`content_policy:read/manage` 权限与多实例周期刷新;运行时使用不可变 RE2 编译快照。
- 内容策略支持 `audit``block``redact`,按 endpoint、模型和 API Key 限定范围;只遍历提示词文本字段,不改写工具 schema、图片/Base64 等非文本载荷。
- 默认敏感信息脱敏策略、阻断响应、`X-Gateway-Content-Redacted` 响应头,以及不包含原始命中值的审计标签。
- Art 管理端内容策略页面,可维护优先级、范围、规则、替换文本和启停状态。
- 带生效/失效时间的模型价格版本,支持 Provider + 精确模型或末尾 `*` 前缀,精确规则优先并使用请求时刻选择版本。
- 输入/输出价格均以“每百万 Token 的微货币单位”精确存储;usage 完成时核算请求成本,审计与按日聚合均保存 `cost_microunits`
- `pricing:read/manage` 权限、模型价格管理 API/Art 页面,审计与用量页面展示估算成本。
## 已完成:M4 Prompt、知识、工具、应用编排和通知
- Prompt 分类/模板和只增不改的版本,显式变量声明、必填/默认值校验、历史版本激活、管理端和 API Key 渲染接口。
- PostgreSQL 知识库保存不超过 2 MiB 的提取文本,不依赖 MinIO/S3;段落感知重叠分块,FTS 与中文二元词片混合召回,并通过 `Retriever` 接口隔离未来向量实现。
- 声明式 HTTP 工具、基础 JSON Schema 入参验证、KEK 加密请求头、注册及拨号时 SSRF 校验、禁止重定向、1 MiB 响应上限和工具调用记录。
- AI 应用草稿、引用完整性校验、不可变发布版本和运行记录;应用入口组合 Prompt/RAG/OpenAI 工具调用循环,内部每个模型请求仍走原网关全治理链路。
- 通知通道与可靠投递记录,独立 Worker 消费 outbox,精确/前缀事件订阅、HMAC-SHA256 签名、失败留痕与人工重试。
- 内容策略命中由异步审计批处理在同一事务写入 `content_policy.matched` outbox,不在模型请求热路径同步投递 Webhook,也不包含原始敏感命中值。
- Art Design Pro 管理端新增 Prompt、知识库、工具、应用和通知五个页面,权限与动态菜单均由服务端控制。
- 独立前端多阶段镜像将 Art 管理端生产构建交付给 Nginx,并同源代理管理 API、模型 API、SSE 与健康检查;Compose 可直接启动完整管理面。
## 已完成:M5 契约、迁移与切换工程
- 通过 Python AST 自动盘点旧源码,确认实际为 201 个路由装饰器,而不是早期估算的 141 个;与 OpenAPI 生成可重复的覆盖/替代/退役/缺口矩阵。
- 可选影子中间件只复制确定性采样的非流式 JSON POST,使用独立影子 Key,不转发生产凭据;异步比较状态码和 JSON 结构签名并输出 Prometheus 指标。
- 独立 `gateway-loadtest` 支持并发、时长、超时、错误率和 p95 阈值,输出机器可读 JSON 并以退出码阻断不达标发布。
- 原子 Nginx upstream 切换脚本在目标探活、`nginx -t` 和 reload 任一步失败时恢复备份;提供明确的回退触发条件与演练手册。
- 旧 SQLite 只读 JSONL 导出、逐记录 SHA-256、确定性 UUIDv5 和 PostgreSQL 不可变暂存管道已完成;默认排除敏感列,相同快照幂等,旧记录变化失败关闭。
- 201 条旧路由已全部形成明确决策:85 条同契约覆盖、112 条由新契约替代、4 条基于安全或架构 ADR 退役、未决缺口 0;OpenAPI 0.10.0 覆盖全部 146 条 Go 字面量路由。
## 已完成:M6 门户工作台与扩展治理
- 门户资产目录、Prompt 查看/搜索/收藏、个人审计/Token/成本统计、接入说明和模型访问申请;所有列表按登录用户与部门范围服务端过滤。
- 管理端模型申请审批、事实核验 Provider 设置、作用域策略与核验事件页面;事实核验复用 Provider 加密凭据,不重复保存 API Key/Base URL。
- 已发布应用的门户单轮入口和服务端托管会话;运行凭证使用独立 AEAD purpose 加密,明文不返回浏览器。
- 会话串行租约避免并发回答交错,用户/助手消息以不可变顺序和 SHA-256 哈希链保存,重放前验证完整性,最多 200 条消息。
- 应用目录/资产依赖分析、历史版本回滚、知识文档重新分块、Prompt 详情、运行时快照刷新、系统信息和 24 小时监控汇总。
- Art Design Pro 管理端新增模型治理页面;门户端新增资产目录、Prompt 广场、个人用量与模型权限四个生产页面。
- Compose 同时交付 API、管理端 `8081` 和门户端 `8082`;同一前端 Dockerfile 通过受限 build arg 构建两套独立 SPA。
工程实现与本地生产等价验证已关闭。正式上线仍是外部发布门禁:拿到实际旧数据库脱敏快照和旧密钥迁移授权后执行领域转换,并在真实新旧双系统与生产等价流量下完成影子观察、容量验收及切换/回退演练;这些动作不会在缺少生产数据和授权时伪造为已执行。
## 已完成:M8 基础设施层(P1 对象存储与文件管理)
- 自托管 MinIO 对象存储入基线:`internal/platform/storage`minio-go 适配层)剥离 scheme 推导 Secure,领域层不直接依赖 S3 客户端;`S3_*` 配置默认值 + Validate1512 MiB 上限、endpoint 无路径)。迁移 `000023``gateway.file_objects`uuid/object_key 唯一/sha256/scope/owner 校验 + 部分索引)。
- 文件上传/下载全部经网关代理(MinIO 不暴露主机端口):流式 multipart 上传(`http.MaxBytesReader` + `LimitReader` 超限即拒),`PutObject` 成功后才插元数据行、失败回滚对象;下载 io.Copy 流式 + RFC 5987 `filename*`;删除先删行再 best-effort 清对象(避免孤儿阻塞删除)。
- `FileService` 个人/系统双范围:admin 文件管理(`file:read|manage` RBAC + 管理端文件管理页)与 portal 个人文件仓(严格归属隔离,跨用户读返回 404)。
- Composedev + production)新增 `minio` 服务与 `minio-data` 卷;nginx `client_max_body_size` 32m→256m 盖过 128 MiB 上传上限;`.env.example` / `production.env.example``S3_*`
- 端到端验证:上传→列表→下载往返一致→删除后桶无孤儿;admin 读 portal 文件 404`TestFileObjectLifecycle` 集成测试连真实 MinIO+PostgreSQL 通过;`go build ./...``go vet ./...`、全量单测通过。
- 管理端"文件管理"与门户端"文件仓库"菜单由服务端动态菜单下发。
## 已完成:M8 基础设施层(P2 向量化与语义检索)
- PostgreSQL 换 `pgvector/pgvector:pg17` 镜像(数据卷兼容,先备份再切换);迁移 `000024` `CREATE EXTENSION vector``knowledge_chunks.embedding vector(1024)` 列 + HNSW 余弦索引,并把 `retrieval_mode` CHECK 放宽为 `('postgres_fts','vector','hybrid')` 三态。
- 本地 Ollama`bge-m3`1024 维)生成嵌入,Compose 新增 `ollama` 服务与 `ollama-models` 卷;`OllamaEmbedder` 批量调 `/api/embed`,首见 404 惰性 `/api/pull` 重试一次,按批校验维度,超时 `EMBEDDING_TIMEOUT``EMBEDDINGS_ENABLED=false` 可整体关闭。
- `SemanticRetriever``embedding <=> $1::vector` 余弦距离 + `embedding IS NOT NULL` 过滤)与 `HybridRetriever`FTS + 语义按 chunk 去重融合)实现;`NewRetriever(service, embedder)` 按知识库 `retrieval_mode` 分发,embedder 为 nil 时自动回退纯 FTS(不因缺少 Ollama 而报错)。
- 文档入库/重新分块时在 vector/hybrid 模式下同步批量计算向量(每文档一次 `/api/embed` 收数组);Ollama 故障时优雅降级:文档照常入库、embedding 置 NULL,并在事务内发 `knowledge_document.embedding_failed` 事件。
- 踩坑并修复:pgx v5 `CopyFrom` 对未知 OID(vector)列走二进制编码,字面量随 COPY 上传会被 `vector_recv` 误读为维度数而报 `vector cannot have more than 16000 dimensions`;改为 COPY 仅基础列,随后在同一事务内用 `UPDATE ... FROM unnest($1::uuid[], $2::text[])` 批量回填向量,避免 2 MiB 文档上千条逐条 INSERT。
- 知识库列表新增 `vectorized_chunk_count``count(c.embedding)`),管理端展示"已向量化切片/总切片"覆盖率;创建/编辑知识库可三态选择检索模式,未向量化分块需重新处理才被语义召回。
- `EMBEDDINGS_*` 配置(`OLLAMA_BASE_URL` 默认 `http://ollama:11434``EMBEDDING_MODEL` bge-m3、`EMBEDDING_DIM` 须与 `vector(1024)` 一致、批大小、超时)写入两个 `.env.example` 与 compose anchor。
- 单测 `embedder_test.go`/`retrievers_test.go`httptest 假 Ollama:批量切分、维度不符、404→pull→重试、降级分发)与集成 `TestKnowledgeVectorLifecycle`(真 pgvector+Ollama:导入即向量化、语义命中、embedder 失败时入库 + `embedding_failed` 事件)全部通过;`go build ./...``go vet ./...` 通过。
## 已完成:M8 基础设施层(P3 定时任务调度器)
- 迁移 `000026``gateway.scheduled_tasks``gateway.scheduled_task_runs`:任务配置和执行历史以 PostgreSQL 为权威源;计划触发使用唯一键防重复入队,pending/running/due 部分索引支撑大队列扫描。
- 独立 `gateway-scheduler` worker 解析标准五字段 Cron 与 IANA 时区;到期任务和执行队列均使用 `FOR UPDATE SKIP LOCKED`,支持多副本并发、过期计划合并、执行租约超时回收、有限重试和最终失败事件。
- 任务可调用已发布应用或数字员工;数字员工任务可在员工已有绑定内进一步限制 Skills/MCP 子集。执行 API Key 使用 `scheduled-task-api-key` 独立 AEAD purpose 加密,明文不回显。
- 支持提示词、变量、会话 ID(保留最近五次成功回答作为上下文)和指定通知通道;完成/最终失败写入 outbox,通知 worker 可只投递指定 Webhook,同时物化为创建者站内信。
- 管理 API/页面提供创建、编辑、启停、立即执行、删除和执行历史;RBAC 新增 `scheduled_task:read/manage`,菜单由服务端动态下发。
- `TestSchedulerPostgreSQLLifecycle` 连真实 PostgreSQL + 模拟网关验证:加密凭据解密、手动与计划入队、只执行一次、响应落库、完成事件和下一运行时间推进;Cron 与数字员工绑定子集另有单元测试。
## 已完成:M8 基础设施层(P4 站内消息)
- 迁移 `000025``gateway.inbox_messages`:一条消息一个收件人(admin/portal 跨身份表、不设 FK),sender 广播逐收件人落库;部分唯一索引 `(source_event_id, recipient_kind, recipient_user_id) WHERE source_event_id IS NOT NULL`NULLS NOT DISTINCT)实现事件重放幂等;`(recipient_kind, recipient_user_id, created_at DESC)` 收件箱索引 + 未读部分索引。
- `InboxService``inboxPlan` 纯函数把 outbox 事件类型映射为站内信草稿(`model_access.requested`→通知全部启用管理员、`model_access.decided`→回执申请用户、`marketplace.installed`→通知安装用户、`knowledge_document.ready/reprocessed/embedding_failed`→通知执行管理员、`scheduled_task.completed/failed`→通知创建者);收件人解析区分直取 payload / 查 `model_access_requests` / 全部启用管理员。未读数以 PostgreSQL 为权威源,Redis 仅 PUBLISH 提示(为未来 SSE 预留)。
- 通知 worker 在同一消费循环内物化站内消息(`dispatcher.SetInbox`,为 nil 时不落库、不影响 webhook);物化失败与 webhook 同语义留在 pending 由 reclaim 重试,幂等键保证重放不重复。
- admin HTTP:消息中心(`scope=mine/broadcasts`)、未读数、向 portal(可按部门过滤)或全部 admin 广播、单条已读、全部已读;RBAC 新增 `inbox:read`/`inbox:manage`(auditor 仅读),动态菜单下发"站内消息"入口。portal HTTP:个人收件箱、未读徽标(header bar 30s 轮询)、已读/全部已读。
- 前端:管理端 `gateway/inbox`(我的消息/已发送广播双 Tab + 广播对话框)、门户端 `portal/inbox`;门户顶栏 mail 铃铛 + 未读角标。
- 集成测试 `TestInboxMaterializeAndBroadcast` 连真实库验证:事件物化→未读计数→重放幂等→已读回执→管理员广播→broadcasts 全局列表→全部已读。
- 踩坑并修复:集成测试清理 DELETE 中同一参数同时比较 uuid 列与 jsonb text 提取,PG 无法推断类型报 `text = uuid`SQLSTATE 42883)且 `_, _` 吞错导致重跑残留累加——改为显式 `::uuid`/`::text` 并补全按收件人删除。
## 已完成:M9 智能体与可观测(P1 LLM Trace
- 迁移 `000027``gateway.agent_traces``gateway.agent_trace_spans`,并把 `trace_id` 关联到应用/数字员工运行记录;Trace 类型区分 application/digital_employeespan 类型覆盖 model/retrieval/tool。
- 应用和数字员工运行链路接入 Trace:每轮受治理模型调用记录 Provider、模型、HTTP 状态、Token、轮次和耗时;知识库检索记录命中数;工具/MCP 执行记录名称、轮次、调用 ID、状态、耗时和错误。调用正文、工具参数和模型回答不写入 Trace。
- Trace 写入失败是 best-effort,不改变模型调用响应;Trace 完成时同步回填运行计数、最终状态和错误,保留现有 `application_runs`/`digital_employee_runs` 查询兼容性。
- admin API`/api/v1/admin/traces` 列表过滤(时间、目标、状态、Request ID)与 `/traces/{id}` span 详情;新增 `trace:read` 权限,operator/auditor 可读,动态菜单下发 LLM Trace 页面。
- 管理端新增 LLM Trace 时间线页面,展示模型/检索/工具链路、耗时、Token、Provider 和失败原因,明确提示不保存正文。
- 验证:`TestTracePostgreSQLLifecycle` 验证存储生命周期;`TestWorkbenchPostgreSQLLifecycle` 验证真实应用运行自动产生模型/检索 Trace。
## 已完成:M9 智能体与可观测(P2 智能体会话)
- 复用 Trace 元数据聚合统一智能体会话中心:有 `X-Gateway-Conversation-ID` 的请求按会话聚合,无状态请求按 `request:<request_id>` 回退,区分 application/digital_employee。
- admin API`/api/v1/admin/agent-sessions` 支持时间、类型、目标编码、会话 ID 和数量过滤,返回 Trace 数、最近 Trace、状态、模型/工具/检索累计计数与最近活动时间;不读取或返回对话正文。
- 管理端新增“智能体会话”页面,可查看会话聚合指标并打开最近 Trace 时间线;复用 `trace:read` 权限和动态菜单。
- 验证:`TestTracePostgreSQLLifecycle` 增加会话聚合查询断言,admin 前端生产构建通过。
## 已完成:M9 智能体与可观测(P3 节点注册与心跳)
- 迁移 `000028` 新增 `gateway.agent_nodes`:节点编码、节点类型、Endpoint、公共/私有节点池、能力/元数据、令牌哈希和最后心跳信息;令牌只在登记或轮换响应中返回一次。
- admin API:节点列表、登记、编辑、删除和令牌轮换;`agent_node:read/manage` 权限分别授予 auditor/operator,管理端新增节点监控页面。
- 节点 API`POST /api/v1/agent/nodes/{code}/heartbeat` 校验 `X-Agent-Token`,刷新版本、能力、元数据、错误和来源 IP;根据 90 秒心跳窗口派生 pending/online/offline/disabled 状态。
- 当前 P3 明确不假装覆盖远程裸机安装、节点任务下发和真实执行路由;公共/私有池字段与能力元数据为下一阶段路由实现保留契约。
- 验证:`TestAgentNodePostgreSQLLifecycle` 覆盖登记、编辑、心跳、令牌轮换和旧令牌失效。
## 已完成:M9 智能体与可观测(P4 节点池路由预览)
- admin API`POST /api/v1/admin/agent-nodes/route-preview` 按公有/私有池、池编码、能力标签和 90 秒在线窗口筛选候选节点。
- 选路使用 `sha256(request_key + "\x00" + node_id)` 的稳定排序,同一个 request key 在候选集合不变时会得到同一首选节点;响应同时返回候选列表、选路策略和无节点/无能力原因。
- 管理端节点页面新增“路由预览”对话框,明确展示首选节点和候选顺序;该接口只读,不访问节点 Endpoint、不创建任务,也不代表远程执行路由已经完成。
- 增加纯函数筛选/稳定性测试,并在 `TestAgentNodePostgreSQLLifecycle` 中覆盖真实在线节点的能力路由预览。