Files

223 lines
22 KiB
Markdown
Raw Permalink 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.
# API-first Multi SimAdmin 项目章程
> 文档状态:Phase 0.1 基线<br>
> 冻结日期:2026-07-15<br>
> 上游证据基线:[`3899/SimAdmin@58e220411d6599609f0eeda01eb7016e9212f970`](https://github.com/3899/SimAdmin/commit/58e220411d6599609f0eeda01eb7016e9212f970)<br>
> 适用范围: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 restartR3)真实写验收;不安全的 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` 的 AttemptAttempt 的参数摘要、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/117capability 五态;安全执行管线 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 或单元测试通过均不构成项目完成。