# MiragenFlow 渠道、模型、分组与智能调度重构计划 状态:待审批。当前只完成计划评审和计划修订,未修改业务代码,未启动浏览器,未创建展示用渠道,未执行浏览器模拟。 本文件是审批稿。负责人批准前,任何代理不得执行下面的代码改动;批准后仍须按阶段完成和验收,不得把“写入计划”当成“功能完成”。 ## 0. 评审结论 ### 0.1 总结 现有计划的业务方向是清楚的:渠道负责接入和发现模型,模型负责公开能力与分辨率,分组负责模型和渠道的路由关系,健康负责运行状态,任务负责生命周期,价格和套餐负责计费约束,审计负责追溯。 但原计划不能直接执行,原因是它把目标状态和当前状态混在了一起,并遗漏了几个会阻断实施的前置关系: 1. 当前模型仍然承担价格、数量、并发和 `channelGroupId`;直接删除这些字段会破坏任务创建和价格链路。 2. 计划没有明确一个公开模型能否属于多个分组;没有这个基数规则,任务创建无法确定唯一路由。 3. 当前分组只保存全局渠道集合和顺序,计划却要求“某个模型下支持哪些渠道”,数据结构尚未表达模型和渠道的对应关系。 4. 计划要求智能调度、容量和半开状态,但当前 `ProviderChannel` 和 `ChannelGroup` 没有策略、容量或半开状态字段。 5. 当前健康接口从任务 attempts 临时聚合,既不是独立探活样本,也没有每个渠道固定 12 个样本。 6. OpenAI 图片 provider 当前明确不支持查询未知任务;自动 unknown 对账不能作为本阶段已实现功能。 7. 当前人工对账会用 `Math.min` 静默截断超额金额;当前测试也验证的是截断成功,与本计划的 422 规则相反。 8. 当前审计详情仍可能直接返回 `before`/`after`,列表分页也是前端本地分页;这些无法仅靠页面调整解决。 本次修订将每个目标拆成“现状、代码改法、目标行为、测试、完成条件”,并把相互依赖的改动放在正确顺序中。 ### 0.2 已确认的代码事实 | 事实 | 代码依据 | 对计划的影响 | | --- | --- | --- | | 后台模型、渠道、分组、健康和价格路由都挂在同一个 `BusinessPage` | `admin/src/router/index.ts:54-60` | 先按业务职责拆分页面逻辑,再决定是否拆成独立文件;不重复引入 UI 库 | | 模型仍包含价格、数量、并发和分组 ID | `packages/contracts/src/index.ts:127-146`、`server/src/store.ts:120-128` | 必须先建立价格规则、运行限制和模型分组关系,再移除旧字段 | | 任务创建直接读取上述旧字段 | `server/src/app/http.ts:873-926` | P1 不能只改表单;必须同步改任务创建、快照和计费解析 | | 渠道已有 `displayModelId/requestModelId` 映射类型,但仍保留旧的 `providerModelId` 直连路径 | `server/src/store.ts:10-11`、`server/src/adapters/provider.ts:14-20`、`server/src/app/http.ts:2320-2332` | 删除旧路径,所有 provider 请求必须经过渠道模型实体或显式映射 | | 渠道 PATCH 会按 `priority` 回写所有所属分组顺序 | `server/src/app/http.ts:2427-2429` | 渠道属性和分组路由排序必须完全隔离 | | 分组排序只检查全局渠道存在,不检查是否属于该分组 | `server/src/app/http.ts:2548-2554` | 排序接口必须检查成员集合完全一致、无遗漏、无越组成员 | | 任务已有基础路由快照和 unknown 保留预留 | `server/src/app/http.ts:915-926`、`server/src/jobs/task-worker.ts:435-517` | 保留已有能力,但扩展快照、候选过滤和严格重试语义 | | worker 当前只按启用和 `health !== open` 过滤 | `server/src/jobs/task-worker.ts:426-435` | 智能调度必须抽成可测试的候选过滤和排序纯函数 | | 熔断当前冷却约 30 秒,直接回到 `degraded`,没有 `half-open` | `server/src/jobs/task-worker.ts:429-433`、`server/src/jobs/task-worker.ts:487` | 重新定义状态机、探测租约和冷却配置;不能只改展示文字 | | 健康接口返回单个 `groupId`,并从任务 attempts 计算 | `server/src/app/http.ts:3053-3064` | 新增健康样本来源和 `groupIds` 数组;不得把前 12 个渠道误当成 12 个历史样本 | | OpenAI 图片查询返回 `PROVIDER_QUERY_UNAVAILABLE` | `server/src/adapters/provider.ts:250-257` | 当前 unknown 只走人工对账;自动查询列为后续能力门槛 | | 对账任务和账本在事务中,但审计调用在事务外 | `server/src/app/http.ts:2875-2934` | 明确审计原子性方案,不能在计划中笼统写“有事务和审计” | | 持久化仓储主要保存 JSON snapshot | `server/migrations/0002_v1_domain.sql:61-118`、`server/src/infra/repository.ts:561-583` | 新结构要有 snapshot schema 版本和恢复策略;不凭空假设已有独立实体表 | | 测试 fixture 会创建渠道和分组 | `server/src/store.ts:136-143` | 禁止展示 mock 数据,但允许隔离测试 fixture,并验收生产不注入 fixture | ### 0.3 本次明确的架构决策 1. 第一阶段目标是 8 个后台业务模块:渠道、模型、分组、健康、任务、价格、套餐、审计。 2. 人工对账是任务中心中的独立 Tab/筛选视图,不新造一个与任务生命周期脱节的页面;需要独立深链接时再增加 `/reconciliation`,但不是第一阶段前置条件。 3. 模型发现是渠道管理中的主动操作,不单独建“模型发现”页面。 4. 一个公开模型在第一阶段只能归属一个启用的路由分组;一个分组可以管理多个公开模型。这样保留当前任务按模型找到唯一分组的语义,避免一次任务同时命中多个未知策略。 5. 分组内部使用“模型 × 渠道”关系。一个渠道可以支持同一分组中的部分模型,不要求一个渠道覆盖分组内所有模型;每个模型有独立渠道池、独立顺序和独立重试预算。 6. 分组默认使用严格优先级。只有管理员明确选择“同优先级智能调度”时,才在最低可用优先级层内使用健康、容量、成功率和 P95 排序;智能指标不能让低优先级渠道越过高优先级渠道。 7. `publicModelId` 是平台公开 ID,`requestModelId` 是供应商请求 ID。两者在契约、存储、页面、路由和 provider 边界都必须保持分离。 8. 模型目录只负责公开身份、能力和分辨率。价格倍率归价格规则,数量硬上限归系统运行策略,套餐并发归套餐/租约;这些字段不回到模型弹窗。 9. 当前 OpenAI 图片 provider 没有可查询的异步任务协议,所以 unknown 结果本阶段不自动查询、不自动退款、不自动切换,只保留预留并进入人工对账。 10. 健康趋势固定显示 12 根条。绿、黄、红、灰、蓝只表示样本或探活阶段,不能替代当前调度资格。 ## 1. 范围和禁止事项 ### 1.1 本次范围 - 管理后台渠道、模型、分组、健康、任务、价格、套餐和审计的职责重构。 - `packages/contracts` 中公开模型、渠道模型、路由快照、价格快照、健康和管理分页契约。 - `server/src/store.ts`、`server/src/app/http.ts`、`server/src/adapters/provider.ts`、`server/src/jobs/task-worker.ts` 及持久化 snapshot 结构。 - 管理台请求封装、TDesign 表格/弹窗/Tab/下拉和现有本地 Lineicons 的业务接入。 - 代码级测试、类型检查、Lint、差异检查和必要的持久化/迁移回归。 - `CHANGELOG.md`、双语 `todo`、双语 `pending-test`;用户确认浏览器测试通过后再更新正式功能文档。 ### 1.2 明确不做 - 不修改 `web/src/pages/canvas`、`web/src/components/canvas`、`web/src/stores/canvas`、`web/src/lib/canvas`,也不修改任何画布底层架构。 - 不把现有 TDesign 后台重写成 Ant Design、Lucide 或其他控件库;继续使用锁定版本的 TDesign 和本地 `admin/src/components/LineIcons.jsx`。 - 不使用 CDN、WebFont、原生 `