# API-first Multi SimAdmin 项目章程
> 文档状态:Phase 0.1 基线
> 冻结日期:2026-07-15
> 上游证据基线:[`3899/SimAdmin@58e220411d6599609f0eeda01eb7016e9212f970`](https://github.com/3899/SimAdmin/commit/58e220411d6599609f0eeda01eb7016e9212f970)
> 适用范围:Multi SimAdmin V1 的产品、API/BFF、Web 控制台、测试与迁移决策
> Phase 0.1 文档导航:**项目章程**(本文)|[现状审计](./current-system-audit.md)|[角色与流程](./personas-and-workflows.md)|[信息架构](./information-architecture.md)|[117 项验收账本](./operation-acceptance-matrix.md)
## 1. 立项结论
现有产品是“状态卡片墙 + 原站 iframe + 手工 API 请求框”,不能支撑多实例日常管理。项目从产品信息架构和控制面契约开始重构,建设一个 **API-first、能力感知、安全可确认、任务可追踪、操作可审计** 的多实例 SimAdmin 控制台。
“API-first”在本项目中的可验收含义是:
1. 所有核心管理能力由明确建模的控制面 API 提供,不以 iframe 作为功能依赖。
2. 固定上游基线的每个业务操作均进入 OperationRegistry,状态只能是“已支持、版本不支持或有理由暂缓”,不得静默遗漏。
3. 用户通过业务页面和结构化表单完成操作,不手写 HTTP 方法、路径和任意 JSON。
4. UI、BFF 授权、能力目录、OpenAPI 和契约测试共享同一操作事实源。
5. 所有改变设备状态的请求都经过风险分级;R3 全部 Job 化;R2 默认 Job 化,仅允许 OperationRegistry 对经理由与测试证明可同步安全执行的单目标操作显式声明例外;批量 R2 仍全部 Job 化。R2/R3 均经过预检、专用确认和审计。
## 2. 问题陈述
当前系统存在以下不可通过局部美化解决的问题:
- 主要路由、DOM 拼接、状态和异步行为集中于 `public/app.js`,模块边界与可测试性不足。
- 默认卡片墙强调装饰而非异常定位、筛选、比较和批量管理。
- iframe 会被上游 X-Frame-Options、CSP、跨站 Cookie 阻断,无法作为可靠入口。
- API 工作台把方法、路径和 JSON 正确性转嫁给操作者,且无法表达字段约束、影响范围与兼容性。
- endpoint 分散在前端、catalog、proxy policy 和 status service,存在漂移。
- 当前代理只覆盖 36 个读路径、15 个写路径;上游基线确认有 100 个路径、117 个业务操作(GET 50、POST 62、DELETE 5)。
- 缺少 capability、版本差异、部分失败、陈旧数据、Job、审计和历史状态的一致模型。
## 3. 产品愿景与原则
### 3.1 愿景
操作者无需理解 SimAdmin 内部 endpoint,即可从一个高密度控制台发现异常、进入正确模块、安全修改单台或多台实例,并能回答“谁在何时对哪台实例做了什么、结果如何”。
### 3.2 强制原则
- **异常优先:** Fleet 默认表格按不可达、认证失败、退化、陈旧等异常可见且可筛选。
- **能力驱动:** 不支持的能力不显示为可点击动作;必要时保留解释性占位,不用 `-` 假装正常。
- **目标明确:** 每个写操作都显示实例、当前值、目标值、字段 diff、影响和风险。
- **默认拒绝:** 未注册 operation、未知方法/路径、认证端点通用代理均拒绝。
- **部分结果真实:** Fleet 批量与单实例资源批量分开建模;Fleet 逐实例给出成功、失败、跳过及原因,不制造“全部成功”。具体 `resourceBulk`、`fleetBatchable` 与逐项聚合以验收账本为准。
- **凭据服务端化:** 密码、Cookie、token 不进入浏览器响应、日志、审计或 fixture。
- **高密度但可访问:** 主要工作流兼容键盘和窄屏;视觉不能牺牲错误、焦点和操作可达性。
## 4. 目标与验收指标
| 编号 | V1 目标 | 验收证据 |
|---|---|---|
| G1 | 多实例可发现、可筛选、可比较 | `/fleet` 支持搜索、状态/认证/版本/标签/运营商/信号/异常筛选,支持排序、列显隐、分页、多选 |
| G2 | 完整掌握上游能力 | 固定 commit 的 117 个操作在 registry parity 测试中 117/117 对齐,无重复 operationId |
| G3 | 核心操作不依赖 iframe/手写 JSON | 13 个上游模块均有结构化业务入口;原站仅为 `noopener noreferrer` 新窗口链接 |
| G4 | 状态可诊断 | UI/API 区分 online、offline、auth-required、degraded、unknown,以及 fresh、stale、loading、error、unsupported、partial |
| G5 | 写操作安全 | R1 显示字段 diff;R2/R3 有预检、绑定目标的一次性确认令牌、结果追踪和审计;R3、批量 R2 及无同步安全例外的 R2 有 Job 证据 |
| G6 | 多实例请求受控 | scheduler 具备全局/单实例并发、TTL、jitter、backoff、deadline、取消和熔断;页面刷新不扇出 117 个请求 |
| G7 | 凭据不泄漏 | secret redaction 契约与安全测试证明 password/cookie/token 不出现在响应、日志、audit、snapshot、fixture |
| G8 | 真实可用 | Phase 8 在冻结的真实实例版本/响应基线上逐模块完成读验收,并完成 cellular 至少一个 R1、Notifications channel test、Automation task test,以及专用测试实例上的 service restart(R3)真实写验收;不安全的 OTA apply、删除/通话类动作允许契约+模拟,但必须明确标为模拟,不得计入真实写 |
| G9 | 响应式与可访问 | 可见 Chrome 在 1440、1024、768、390、320 宽度无页面横向溢出、遮挡、重叠或不可达动作;关键流程键盘可完成 |
| G10 | 可安全切换 | 新旧并行、只读 shadow comparison、数据库备份、独立端口和一键回滚均有演练记录 |
## 5. 范围
### 5.1 V1 范围内
1. 实例添加、编辑、删除、标签、连接测试、认证测试、会话登录/登出。
2. Fleet 汇总、状态采集、搜索筛选、异常定位、多选和受控批量动作。
3. 实例模块:overview、cellular、device-network、messages、calls、esim、notifications、automation、ota。
4. 跨实例模块:jobs、audit、settings/instances、settings/system。
5. 117 个上游操作的 registry、schema、风险、超时、兼容性和 UI 策略建模。
6. 控制面 `/api/v1`、OpenAPI 3.1、统一错误、分页/排序/filter、revision、partial result。
7. Job、逐实例 job item、取消/重试、重启恢复;审计与敏感字段脱敏。
8. SQLite 持久化,macOS Keychain 优先保存 secret reference;旧配置预览后幂等导入。
9. SSE 推送实例状态、Job 和 audit 摘要。
10. 单元、契约、集成、组件、E2E、安全、可访问性和真实浏览器验收。
### 5.2 V1 非目标
- 公网 SaaS、租户隔离和多用户 RBAC。
- 任意 REST 客户端、任意 URL/路径代理或插件脚本执行器。
- iframe 集成、复制上游完整页面、自由拖拽 dashboard。
- 复杂告警规则引擎、短信/语音业务运营平台。
- 跨地域 HA、集群调度和云端 secret vault。
- 未经 registry 声明的“专家模式”绕过风险确认。
- 在 Phase 0.1 修改生产代码、删除旧实现、切换 8788 或迁移真实数据。
## 6. 用户与核心任务
| 用户 | 首要任务 | 成功判据 |
|---|---|---|
| 日常运维员 | 找到异常实例并恢复连接/服务 | 2 分钟内从 Fleet 定位异常,进入正确模块,看到结果或 Job |
| 网络/设备工程师 | 诊断蜂窝、射频、WLAN、APN、eSIM | 当前值、能力、版本、采集时间和错误证据同时可见;高风险变更可回溯 |
| 系统维护者 | 配置实例、控制轮询、升级、排障 | 实例 CRUD 和导入不泄漏凭据;OTA/重启有预检、任务与回滚提示 |
| 审计/故障复盘者 | 解释一次变更及部分失败 | 可按时间、实例、operation、风险、结果、requestId 检索完整摘要 |
详细流程见 [personas-and-workflows.md](./personas-and-workflows.md)。
## 7. 状态与风险政策
### 7.1 实例汇总状态
优先级从高到低:
1. `offline`:health/network 不可达或 deadline 超时。
2. `auth-required`:可达但认证缺失、失效或 401。
3. `degraded`:可达且可认证,但一个或多个关键采集项失败/熔断。
4. `online`:关键采集项成功,且未超过 freshness TTL。
5. `unknown`:尚未采集、版本/响应无法判定或初始化中。
汇总状态不得覆盖采集项状态;详情必须展示每项的 `fetchedAt`、`duration`、`freshness`、`support`、`errorCode` 和 HTTP status。
### 7.2 风险等级
| 等级 | 定义 | 最低交互/执行要求 |
|---|---|---|
| R0 | 严格只读查询,包括 health 与 auth-status;不得提交凭据或改变服务端会话 | 无确认;受 scheduler、timeout、cache 控制 |
| R1 | 可逆设置 | 结构化校验,显示当前值→目标值和目标实例,显式提交,写入 audit |
| R1-S | **R1 的会话敏感子类,不是第五个风险等级**:credential verify(提交一次性临时密码)、login、logout;只改变专用认证会话,不改变设备 setup/password | 仅走专用会话 BFF;按 actor+session+instance 限速;审计 actor/session、实例、operation、时间、结果、requestId 等元数据但绝不记录 secret/凭据正文或摘要;超时、401、重连后均禁止自动重放 |
| R2 | 服务影响 | capability/认证/当前状态预检,专用确认,一次性 token,默认 Job 化,逐实例结果;仅单目标 operation 可由 OperationRegistry 显式标记 `syncSafe=true` 例外,且必须同时记录理由并有同步执行安全性/超时/结果落点测试;批量 R2 不允许例外,仍全部 Job 化 |
| R3 | 破坏性或高危 | R2 全部确认与预检要求 + 强确认文案/目标复述 + 不可逆或中断说明 + 失败恢复建议;无同步例外,全部 Job 化 |
认证 `setup/password` 等设备凭据改动是 R3 专用流程并全部 Job 化,不进入通用上游代理;待 Phase 0.2 固定上游请求/响应形态后只能细化 schema、前置条件和恢复步骤,不得降级为 R1-S 或通用代理。实例配置中的本地 SecretStore reference `set/preserve/clear` 仍是 R1,必须与设备密码改动明确分开。
### 7.3 确认令牌唯一规范
本节是 V1 确认令牌的唯一规范;其余 Phase 0.1 文档只能引用本节,不得维护字段子集。R2/R3 `prepare` 生成的短期一次性 token 必须不可篡改地绑定:`actorId`、控制台 `sessionId`、实例 stable ID、实例 config revision、canonical origin、`operationId`、HTTP method、canonical path/query、request body digest、规范化 content type、expiry 和高熵 nonce。
- token 只允许提交一次且短期有效;服务端在**第一次执行尝试入口即原子消费**,即使随后发生字段 mismatch、过期、revision 改变或执行失败也不得恢复或复用。
- canonical path/query 必须使用单一编码/排序规则;执行端重新计算全部绑定字段并常量时间比较摘要。目标、表单、actor/session 或配置变化均要求重新 prepare。
- token 不提供幂等重放语义;网络结果未知时读取目标状态或创建人工核实结果,不自动再次执行。token 本身及 secret 不写日志/audit。
### 7.4 Preparation、Job 与 Attempt 固定模型
`prepare` 先创建独立、短期的 **Preparation** 记录(`prepared | consumed | expired | invalidated`),保存脱敏预检结果和确认令牌引用;它不是 Job。确认请求原子消费 token/Preparation 后,才允许为需 Job 化的操作创建 Job,因而 Job 状态机不含 `preflighting` 或 `awaiting-confirmation`。
- Job 有稳定 `jobId`,状态为 `queued | running | cancelling | succeeded | partially-succeeded | failed | cancelled | unknown-result`。状态机只允许 `queued → running | cancelled`、`running → succeeded | partially-succeeded | failed | unknown-result | cancelling`、`cancelling → cancelled | failed | unknown-result`;`succeeded | partially-succeeded | failed | cancelled | unknown-result` 均为终态。Job 一旦进入终态,状态、结果、JobItems、Attempts 和事件历史严格不可变,不得以 retry、恢复或对账改写。
- 每次实际执行是一个有稳定 `attemptId` 的 Attempt;Attempt 的参数摘要、transport tries、事件和结果一经写入即不可变。**用户发起的 retry 永远创建新 Job**:新 Job 使用新 `jobId`、独立状态和独立 Attempt,`retryOfJobId` 指向本次重试的直接来源 Job,`rootJobId` 指向 lineage 中最初 Job;来源 Job 不新增 Attempt、不重新打开终态。
- 批量父 Job 包含稳定 JobItems。重试失败 items 时创建新的父 Job,只复制用户明确选择且 OperationRegistry 判定可重试的来源 items;每个新 JobItem 使用新 `jobItemId` 并以 `sourceJobItemId` 关联直接来源。成功项绝不复制、重放或因父 Job 重试而执行;未选择、不可重试和 skipped 项也不进入新父 Job。
- 新 Job 的 SSE 事件与 audit 执行记录关联新 `jobId + attemptId`,批量项事件还关联新 `jobItemId`;同时记录 `retryOfJobId`、`rootJobId` 和适用的 `sourceJobItemId`,使 lineage 可双向追踪。Preparation/audit 可用 preparationId 关联,但不能伪装成 Job 阶段。
- 仅在 Job 尚未终态时,registry 明确允许的网络级内部重试才可在**同一 Attempt**内有限执行;必须受次数/deadline/backoff 政策约束并逐次记录 transport try。该机制不得用于非幂等操作,也不得被 UI/user retry 调用;网络结果未知的非幂等操作进入 `unknown-result` 或人工核实,不自动重放。
### 7.5 已登记实例 origin 与 SSRF/LAN 授权
出站 adapter 只能连接管理员显式创建且通过校验的实例 canonical origin;任何页面参数、redirect、上游正文和 operation 参数都不能改变目标。仅允许 `http`/`https`,authority 必须包含明确端口(省略时规范化为协议默认端口),拒绝 userinfo、fragment、空/歧义 hostname、非规范或歧义 IP 写法(包括整数/八进制/十六进制 IPv4、混合编码与 zone-id)。
V1 因本机控制台用途,默认允许 loopback 与 RFC1918 LAN 地址,但**仅限已登记 origin 的精确 scheme+host+port authority**,绝不允许借此扫描任意 loopback/LAN 端口。默认拒绝 link-local、unspecified、multicast、broadcast、云 metadata 地址、公网 IP,以及 IPv4-mapped IPv6、NAT64、6to4/Teredo 等转换/绕过类别;公网放行只能是未来显式政策,V1 不实现。IPv6 私网若未来支持也必须显式列入策略,不能由“非公网”推断允许。
- hostname 每次连接前解析,所有 A/AAAA 结果必须全部属于允许类别;解析结果与实例 revision 一起用于连接,并将实际 socket 目标固定为已验证地址,连接建立时再次验证实际 peer address,禁止 DNS rebinding。
- redirect 默认禁用;若某 operation 经 registry 明确需要,逐跳限制次数并重新执行完整校验,且每跳必须与登记 canonical origin 同 origin,不能用 redirect 改 authority。
- transport 忽略 `HTTP_PROXY`、`HTTPS_PROXY`、`ALL_PROXY`、`NO_PROXY` 等环境代理;HTTP `Host` 与 TLS SNI 必须由登记 origin 构造并与其一致,不接受调用者覆盖。
- 安全测试矩阵至少覆盖允许的已登记 loopback/RFC1918 精确端口,以及未登记端口、协议/userinfo/fragment、各类非规范 IPv4、IPv6 转换、link-local/unspecified/multicast/broadcast/metadata/公网、混合 DNS 结果、DNS rebinding、redirect 跨 origin、代理环境变量和 Host/SNI 覆盖;拒绝用例必须证明未产生外连。
### 7.6 三个正交页面状态
页面状态固定为三个不可互相覆盖的维度:请求执行 `idle/loading/success/error`、数据新鲜度 `fresh/stale/expired/unknown`、能力支持 `supported/unsupported/auth-required/degraded/unknown`。实例汇总状态与这三维并存;详细呈现和组合规则见[信息架构 5.1](./information-architecture.md#51-三个正交维度)。领域/API 枚举只能使用 `fresh`,中文显示为“最新”;禁止 API/数据库枚举使用 `latest`。
## 8. 交付物与阶段门禁
### 8.1 Phase 0.1 交付物
- 本项目章程。
- [现状审计](./current-system-audit.md)。
- [角色与用户流程](./personas-and-workflows.md)。
- [信息架构](./information-architecture.md)。
### 8.2 Phase 0.1 验收清单
- [ ] 四份文档均为中文且互相链接,包含目标、非目标、状态、风险、路由和模块边界。
- [ ] 审计清单逐项标为保留迁移、重写或废弃,并有理由与退出条件。
- [ ] 用户流程覆盖实例接入、Fleet 定位、模块读写、批量动作、Job、audit 和错误恢复。
- [ ] IA 包含全部目标路由及页面 × API 模块 × 状态 × 风险验收矩阵。
- [ ] 上游基线 commit 和“100 路径/117 操作”只作为 Phase 0.2 待 parity 验证的冻结事实,不宣称已完成 registry。
- [ ] `git diff --check` 通过;本任务不修改生产代码,规格与质量复审通过前不提交。
### 8.3 后续阶段门禁
| 门禁 | 进入条件 | 禁止带入下一阶段的问题 |
|---|---|---|
| Phase 0 → 1 | 章程/审计/流程/IA 评审;117 操作证据矩阵与脱敏 fixture 策略完成 | endpoint 不明、风险未分级、页面归属冲突 |
| Phase 1 → 2 | workspace、strict typecheck、OpenAPI、统一错误与 Fastify 骨架通过契约测试 | 无 schema API、无 requestId、控制面路径漂移 |
| Phase 2 → 3 | migration 可重放;旧配置预览/幂等导入;SecretStore 无泄漏;实例 CRUD/连接测试通过 | 明文 secret、不可回滚迁移、含糊 password 更新 |
| Phase 3 → 4 | registry 117/117;capability 五态;安全执行管线 replay/mismatch/expiry 测试通过 | 任意代理、unknown 默认允许、R3 同步执行、批量 R2 同步执行、或 R2 无 `syncSafe` 理由/测试的同步裸执行 |
| Phase 4 → 5 | scheduler 限流/退避/熔断;结构化状态;SSE 重连续传测试通过 | 页面触发请求风暴、空 catch、陈旧数据无标识 |
| Phase 5 → 6 | AppShell、Fleet、实例 CRUD、capability 导航完成多宽度/键盘验收 | Animal Island/iframe/通用 API 工作台残留为主流程 |
| Phase 6 → 7 | 每模块至少一读一写契约与 E2E;真实实例读验收;风险表单符合政策 | 模块仅展示 raw JSON、unsupported 冒充空值 |
| Phase 7 → 8 | Job 可恢复、批量逐项结果、audit 可检索且脱敏 | 部分失败被折叠、任务重启丢失、敏感字段入库 |
| Phase 8 → 9 | 全测试、安全审计、真实 Chrome、独立规格/安全复审无 blocker/major | 用 mock 替代真实验收、可访问性或安全缺口 |
| Phase 9 正式切换 | shadow comparison、备份恢复、回滚演练,新版独立端口稳定 | 直接覆盖 8788、先删旧版再验证 |
### 8.4 可重复验收协议与冻结责任
- **100 实例 fixture:** Phase 0.2 由测试负责人冻结版本化 seed、生成器版本和期望结果;固定包含 online/offline/auth-required/degraded、三维页面状态、能力差异和批量部分失败。相同 seed 必须生成相同 stable ID、origin、数据和排序。
- **Fleet 计时:** Phase 8 在指定浏览器/硬件、冷启动 fixture 后开始;从 `/fleet` 首个可交互帧且筛选控件 enabled 时为 `T0`,到目标实例行可见且目标异常单元格可操作为 `T1`。记录 `T1-T0`、浏览器/硬件、fixture hash;阈值由 Phase 0.2 性能负责人冻结,Phase 0.1 不伪造数值。
- **导航计数:** 从 Fleet `T0` 后第一次用户 click/keyboard activation 起计;筛选/搜索提交算一次,分页或展开算一次,进入目标模块算一次,滚动不算导航但受下一条约束。100 实例 fixture 下应用目标筛选后,目标必须出现在首个 viewport,或仅需一次明确分页/行详情跳转后可见;禁止以逐卡/逐行滚动寻找目标作为通过条件。进入失败采集项详情总计不超过 3 次导航。
- **真实写清单:** Phase 8 必测 cellular 一个 R1、Notifications channel test、Automation task test,以及专用可恢复测试实例的 service restart(R3);均保存上游前后读取、版本、响应摘要、jobId/attemptId、audit 和恢复证据。OTA apply、eSIM/消息/通话删除等若环境不安全,只做契约+模拟并显著标记 `SIMULATED`,不得冒充真实。
- **版本/响应冻结:** Phase 0.2 的上游取证负责人固定 SimAdmin commit/版本、每个操作的脱敏真实响应 fixture、schema 和真实/模拟分类,测试负责人固定环境与指标阈值;Phase 8 QA 按冻结物执行并报告 fixture hash、环境、原始结果和偏差。版本变化必须重新取证和审批,不得静默更新 golden。
## 9. 依赖、约束与风险
- 上游无正式 OpenAPI:以固定 commit 的路由、handler、model、Bruno 和上游前端交叉取证。
- 上游版本差异:capability discovery + compatibility adapter + fixture,不用空值吞差异。
- 多实例扇出:并发限额、TTL、jitter、backoff、deadline、取消、熔断和 SSE。
- 网络盘路径:代码可留在现路径;运行期 SQLite、缓存和 secrets 应落本地数据目录。
- OTA 大文件:专用上传流和进度,不放入普通 JSON proxy;普通 body limit 不作为产品方案。
- UI 偏离运维需求:先验收 IA、表格与完整工作流,再验收视觉。
## 10. 决策与变更控制
以下变更必须更新本章程、IA 或 registry 并经过规格与安全复审:
- 新增/移除一级路由或上游操作;
- 改变风险等级、确认策略或 batchable 属性;
- 改变 secret 存储和日志/audit 字段;
- 将 `unsupported`、`auth-required`、`degraded` 合并为模糊错误;
- 引入通用代理、iframe、绕过 Job 的 R3/批量 R2 快捷入口,或未由 OperationRegistry 以理由和测试明确证明 `syncSafe` 的同步 R2 入口。
## 11. 完成定义
V1 只有在 G1–G10 全部有可重复证据、117 个操作没有静默遗漏、真实实例和真实浏览器验收通过、且 8788 切换具备回滚时才称为“可使用”。单纯页面完成、接口返回 200 或单元测试通过均不构成项目完成。