17 KiB
Multi SimAdmin 现状审计与迁移清单
审计对象:Phase 0.1 时仓库当前实现
产品对照基线:API-first Multi SimAdmin V1
上游证据基线:3899/SimAdmin@58e220411d6599609f0eeda01eb7016e9212f970
1. 审计结论
当前版本可作为安全与并发行为的 characterization 基线,但产品形态、前端、控制面契约和 API 能力模型均不适合继续增量扩展。迁移策略不是原地美化,而是:
- 保留迁移 已验证的安全/一致性思想,并用 TypeScript 契约和测试重建;
- 重写 产品 IA、控制面、状态采集、持久化和全部业务 UI;
- 废弃 iframe、Animal Island、通用 API 工作台和分散 endpoint 清单;
- 新旧系统并行运行,到 Phase 9 验收后才删除旧实现。
本审计中的“保留”不等于复制旧文件;除非另有说明,均指保留行为和测试意图,在新架构中重新实现。
2. 当前系统事实
2.1 前端
| 证据 | 当前行为 | 缺口 |
|---|---|---|
public/index.html |
单页 DOM 包含首页卡片墙、详情、iframe、API 工作台及多个 dialog | 不是业务路由;页面/模块不可深链;视图与能力无对应关系 |
public/app.js |
集中管理全局数组/Map、DOM 拼接、路由、CRUD、状态刷新和代理请求 | 单文件高耦合;业务 schema 缺失;无法按模块独立契约和 E2E |
public/styles.css、public/styles/* |
Animal Island 卡片视觉与响应式样式 | 信息密度低;默认工作面不是异常优先表格 |
public/router/view-router.js |
home/detail/rawpage/api 四视图切换 |
无 /instances/:id/<module> 路由,不支持浏览器级导航语义 |
public/state/request-coordinator.js |
latest-wins/owner 相关防陈旧思想 | 可迁移思想;需要与 TanStack Query key、AbortSignal、instance ownership 结合 |
public/state/operation-draft.js |
区分读写并创建代理草稿 | 无业务字段 schema、风险等级、capability 和兼容性 |
public/infrastructure/api-client.js |
封装当前本地 API | 将由 OpenAPI 生成客户端和模块 query/mutation 取代 |
2.2 后端
| 证据 | 当前行为 | 缺口 |
|---|---|---|
server/config/file-config-store.js |
0600 临时文件、fsync、rename 原子写;串行事务;immutable snapshot;revision;密码 preserve/set/clear | 文件配置不适合历史状态、Job、audit;密码仍在配置模型中,需要 SecretStore 引用 |
server/clients/client-registry.js |
staged reconcile;创建失败回滚;替换/删除清理 client 和 CookieJar | 需持久实例模型、显式生命周期和可观测错误;行为应保留 |
server/core.js |
每实例客户端、CookieJar、认证会话、URL/请求处理 | 需迁入 adapter 并补版本兼容、结构化错误、deadline/取消和 secret redaction |
server/proxy/policy.js |
精确读写 allowlist;认证端点拒绝通用代理;未知默认拒绝 | endpoint 清单手写且不完整;风险只有 dangerous 布尔值 |
server/proxy/confirmations.js |
token 绑定实例、revision、origin、method、规范化 target、body digest;过期且一次消费 | 应扩展 operationId、query/content-type、nonce/actor;R3 与批量 R2 全部进入 Job/audit,R2 默认进入,仅 registry 有理由和测试的 syncSafe 单目标例外 |
server/status/service.js |
health/auth 后并发读取 8 个固定 endpoint,产生 summary | 内层 catch {} 丢失证据;无 freshness/support/capability;页面刷新造成多实例扇出 |
server/app.js |
Fastify、loopback Host/Origin、实例 CRUD、login/logout、status、proxy、静态资源 | API 无 /api/v1 统一契约;catalog 与 policy/status 重复;bodyLimit 60MB 不是 OTA 上传方案 |
test/*.test.js |
核心、安全、UI 源码契约等测试 | 安全测试可作 characterization;源码正则 UI 测试不能替代组件/E2E/视觉测试 |
2.3 API 覆盖
DEFAULT_READ_PATHS:36 个路径。DEFAULT_WRITE_PATHS:15 个路径键。server/app.js的 catalog:7 个分组,并再次硬编码 endpoint。status/service.js:另有 8 个固定采集 endpoint。public/app.js:再次维护模块/快捷 endpoint。- 对照冻结上游研究结果:100 个路径、117 个业务操作(GET 50、POST 62、DELETE 5)。完整 parity 将由 Phase 0.2 的证据矩阵和测试确认。
结论:当前系统不存在一个能回答“此操作是否存在、支持何版本、请求/响应是什么、风险是什么、该如何展示”的单一事实源。
3. 保留 / 重写 / 废弃决策
3.1 保留迁移
| 资产/思想 | 新位置 | 必须保留的可验收不变量 |
|---|---|---|
| 原子写与串行事务 | SQLite transaction、migration、备份/恢复;导入器保留旧文件只读 | 并发变更不丢失;失败不发布新 revision;旧文件不被导入过程修改 |
| immutable snapshot/revision | instances/app settings revision、ETag/If-Match | 写确认绑定准备时 revision;配置变化后旧 token 失效 |
| URL/SSRF 校验 | packages/simadmin-adapter + API schema |
仅连接管理员已登记 canonical origin;loopback/RFC1918 也只限精确 authority;地址/DNS/redirect/proxy/Host/SNI 的完整允许与拒绝政策以章程 7.5为准 |
| 每实例 CookieJar | adapter client/session registry | Cookie 不跨实例;logout/reconcile/delete 清理会话;Cookie 不发给浏览器 |
| 密码登录与临时凭据 | 专用会话 BFF + SecretStore | health/auth-status 严格 R0;verify/login/logout 为 R1-S,限速、无 secret 元数据审计、禁止重放;本地 reference 只有 set/preserve/clear 三态;设备 setup/password 为 R3 专用 Job |
| ClientRegistry staged reconcile | adapter client manager | 新 client 全部可创建后才切换;失败清理 staged client 且旧集合继续可用 |
| 路径规范化与精确授权 | OperationRegistry 生成执行策略 | 未注册 operation 默认 403;认证端点禁止通用代理;double decode 测试通过 |
| 敏感请求头过滤 | adapter transport | Host/Cookie/Authorization 等仅由服务端构造;用户输入不能透传 |
| 一次性确认令牌 | prepare → confirm → execute → audit |
不在本表维护字段副本;严格实现章程 7.3 唯一规范,包括 actor/session 绑定与第一次尝试即消费 |
| latest-wins/owner 校验 | Query key、AbortSignal、mutation owner | 切换实例后旧响应不能覆盖新实例;dialog 关闭后完成响应不能触发动作 |
| 现有安全测试意图 | 新 unit/integration/security suites | Host/Origin、SSRF、代理、确认、secret、并发场景均有等价或更强测试 |
3.2 全面重写
| 范围 | 目标 | 退出条件 |
|---|---|---|
| 产品信息架构 | Fleet + 实例模块 + Jobs/Audit/Settings | 所有目标路由可深链;模块归属无冲突;capability 控制导航 |
| Web 前端 | React/Vite/Router/Query/Table/Form | 不从旧 HTML/DOM 拼接迁移 UI;组件、E2E、axe 和多宽度验收通过 |
| 控制面 API | Fastify /api/v1 + OpenAPI 3.1 |
统一 error envelope、requestId、分页、filter、revision、partial result |
| Operation/capability | 单一 registry | 117/117 parity;每项有 schema、风险、timeout、兼容和 UI 策略 |
| 状态采集 | scheduler + snapshot + SSE | 限流/TTL/jitter/backoff/deadline/取消/熔断;无空 catch |
| 数据持久化 | SQLite + Drizzle + migration | instances、tags、capabilities、snapshots、jobs/items、audit、settings、secret refs 完整 |
| Secret 管理 | Keychain 优先,DB 仅 reference | password/cookie/token 不进入响应、日志、audit、snapshot、fixture |
| 业务操作 UI | 按模块结构化读页和写表单 | 不要求手写路径/JSON;unsupported/auth/stale/partial 明确 |
| Job 与审计 | 持久任务、逐实例 item、可检索 audit | R3 全部、R2 默认且批量 R2 全部 Job 化;同步安全 R2 例外也可追踪;进程重启恢复;逐实例结果不丢失 |
| 测试体系 | unit/contract/integration/component/E2E/security/visual | mock 与真实实例职责分离;每模块至少一读一写 E2E |
3.3 最终废弃
| 旧资产 | 废弃原因 | 删除门禁 |
|---|---|---|
public/index.html |
单页静态产品骨架不可扩展 | 新版 Phase 8 全验收,Phase 9 切换与回滚演练完成 |
public/app.js |
状态、路由、DOM 和 API 高耦合 | 同上;关键行为已由 E2E/characterization 覆盖 |
public/styles.css、public/styles/* |
Animal Island 视觉不符合高密度运维 | 新设计系统多宽度验收通过 |
| Animal Island / “SIM 岛”主视觉 | 装饰优先且占据工作空间 | 不作为新版主题或默认视图保留 |
| iframe 一级页面 | 受 CSP/XFO/Cookie 影响,无法保证功能 | 原站仅保留安全新窗口链接 |
| 通用 API 工作台 | 要求手写方法/路径/JSON,绕开业务 schema | 所有安全开放操作具备结构化入口;诊断也只能使用 registry operation |
| 手写 catalog/read/write/status endpoint 列表 | 多事实源漂移 | OperationRegistry 可生成授权、目录、OpenAPI、类型和 parity 测试 |
| 源码正则型 UI 测试 | 验证文本而非用户行为 | 由 component/Playwright/axe/visual test 替代 |
| 60MB 普通代理 body 作为 OTA 方案 | 缺少流式上传、进度和资源约束 | OTA 专用上传 API 完成 |
4. 状态模型审计
4.1 当前问题
当前 Fleet 主要把实例归入 online/auth/offline/unknown,但状态采集会吞掉单 endpoint 错误,且 reachable、authenticated、HTTP status、summary 的含义可能互相冲突。无法判断:
- 数据是新鲜还是上次成功缓存;
- 404 是“不支持”还是路径错误;
- 401 是整实例未登录还是单能力需要认证;
- 某模块失败时实例应是离线还是退化;
- 批量操作中哪些实例未执行。
4.2 目标模型
每个采集项必须包含:
value | null
fetchedAt
receivedAt
durationMs
freshness: fresh | stale | expired | unknown
support: supported | unsupported | auth-required | degraded | unknown
result: idle | loading | success | error
errorCode | null
httpStatus | null
requestId
sourceVersion | null
实例汇总状态采用 offline > auth-required > degraded > online > unknown 的确定性优先级,但 UI 同时显示 freshness。不得将 unsupported 转换成 null 后显示 -。三个正交维度引用章程 7.6;领域/API 枚举统一为 fresh(中文“最新”),禁止 latest。
Job 使用以下状态机(Preparation 独立,不是 Job):
queued → running | cancelled
running → succeeded | partially-succeeded | failed | unknown-result | cancelling
cancelling → cancelled | failed | unknown-result
succeeded/partially-succeeded/failed/cancelled/unknown-result 是终态;Job 到达终态后,其状态、结果、items、Attempts 与事件历史严格不可变。Preparation 使用 prepared/consumed/expired/invalidated。Job item 独立使用 queued/skipped/running/succeeded/failed/cancelled;Job 汇总不得覆盖 item 原因。
用户 retry 总是创建带新 jobId、独立状态和独立 Attempt 的新 Job,retryOfJobId 指向直接来源,rootJobId 指向最初 Job。批量重试另建父 Job,只复制明确选择且可重试的失败 items,每个新 item 记录 sourceJobItemId;成功项绝不复制/重放。SSE/audit 关联新 jobId+attemptId(批量另含新 jobItemId),并保留 retry/root/source lineage。Job 尚未终态时,仅幂等且 registry 明确许可的网络级内部重试可在同一 Attempt 内有限执行,并逐次记录 transport try;不得用于非幂等操作。完整不变量以章程 7.4为准。
5. 安全与可靠性差距
| 领域 | 已有基础 | 必补缺口 | 验收 |
|---|---|---|---|
| SSRF/URL | 当前 core/schema 有校验思想 | 已登记 origin 精确授权;只允许 http(s);规范 IP;loopback/RFC1918 精确端口;拒绝 link-local/metadata/公网/转换地址;全地址 DNS 固定与 peer 复验;redirect 同 origin;忽略代理;Host/SNI 一致 | 章程 7.5矩阵逐类通过,拒绝 fixture 不产生外连 |
| Host/Origin | loopback authority 精确检查 | 新 Fastify 插件化、proxy headers 场景明确 | inject + 真实浏览器跨源测试 |
| 代理授权 | 精确 allowlist、auth path 拒绝 | registry 生成,参数 schema,未知 operation 默认拒绝 | 117 operation 与授权快照一致 |
| 确认 | 一次性 token 和 body digest | 章程唯一 token 规范;Preparation 与 Job 分离;R3/批量 R2 全部 Job,R2 默认 Job;syncSafe 例外须有理由和专项测试 |
首次 mismatch 也消费;replay、换 actor/session/实例/revision/origin/operation/method/path/query/body/content-type 均失败;同步例外无裸执行 |
| Secrets | 响应有 redaction | Keychain/reference、日志/audit/snapshot/fixture 全链路 | secret canary 全仓输出扫描为零 |
| 请求调度 | 单次 Promise.all | 限流、TTL、退避、熔断、deadline、取消 | 大 fleet 压测不超过配置并发 |
| 错误表达 | 部分 status/error 字段 | 统一 envelope 和 item 级错误;禁止空 catch | 每种失败可定位且带 requestId |
| OTA | 60MB bodyLimit | 流式上传、大小/类型校验、进度、取消、Job | 50MB 测试不进入 JSON 解析/审计正文 |
6. 迁移顺序与护栏
- Phase 0: 冻结文档、上游 operation 证据、脱敏 fixtures;生产代码不动,规格与质量复审通过前不提交。
- Phase 1–4: 新 monorepo/API/数据/adapter/status 与旧服务并存;禁止直接替换 8788。
- Phase 5–7: 新 Web、模块、Jobs、Audit 只调用
/api/v1;不从旧通用代理新增功能。 - Phase 8: 真实浏览器、真实实例、安全和独立复审;blocker/major 必须修复。
- Phase 9: 独立测试端口运行;导入旧配置预览;只读 shadow comparison;备份/回滚演练;最后切换。
- 稳定窗口后才删除旧
public/、旧 endpoint lists 和源码正则 UI tests。
禁止事项
- 在 registry 完成前继续向多个旧 endpoint 数组手工补路径。
- 为赶进度把
unsupported当成功空值、把失败catch {}。 - 将 Keychain 不可用时自动降级为明文密码而不阻断/告警。
- 让前端直连实例、持有 Cookie/密码或拼接任意上游路径。
- 以测试假服务替代真实 SimAdmin 最终验收。
附录 A:主要文件覆盖规则与清单(Phase 0.1 snapshot)
“主要文件”机械定义为 git ls-files 'public/**' 'server/**' 'test/**' 的全部跟踪文件;评审用命令输出必须与下列清单逐项相等。新增/删除匹配文件必须在同一变更更新本附录,并在 3.1–3.3 按所属规则获得结论,避免靠人工判断“主要”。
- 前端全面重写:
public/app.js、public/domain/formatters.js、public/index.html、public/infrastructure/api-client.js、public/router/view-router.js、public/state/fleet-view-model.js、public/state/operation-draft.js、public/state/request-coordinator.js、public/styles.css、public/styles/base.css、public/styles/components.css、public/styles/responsive.css、public/styles/tokens.css。其中 request coordinator 的 latest-wins/owner 行为不变量按 3.1 保留迁移;旧文件仍按 3.3 门禁最终废弃。 - 后端保留行为并重写到新架构:
server/app.js、server/clients/client-registry.js、server/config/errors.js、server/config/file-config-store.js、server/config/schema.js、server/core.js、server/index.js、server/proxy/confirmations.js、server/proxy/policy.js、server/proxy/routes.js、server/proxy/service.js、server/status/routes.js、server/status/service.js。可保留不变量只限 3.1 明列的原子写/revision、client staged reconcile、Cookie 隔离、路径默认拒绝、header 过滤、确认与安全测试意图;其余控制面、持久化、adapter、scheduler 按 3.2 重写,旧 endpoint/proxy/status 多事实源按 3.3 废弃。 - 测试迁移意图、废弃实现形式:
test/phase-one-architecture.test.js、test/phase-one-blockers.test.js、test/phase-two-security.test.js、test/phase-two-ui.test.js、test/server-core.test.js、test/ui-contract.test.js。安全/并发 characterization 意图按 3.1 保留并增强;源码正则 UI 断言按 3.3 删除门禁由 component/E2E/axe/visual 替代。
7. 审计验收清单
- 每个当前主要文件均有保留/重写/废弃结论。
- 保留项写明行为不变量,而非笼统“复用代码”。
- 废弃项有删除门禁,不在并行迁移期提前删除。
- 当前 API 覆盖与上游基线差距有明确数字和 Phase 0.2 验证责任。
- 状态、Job、capability 和错误模型足以区分 loading/error/stale/unsupported/auth/partial。
- 安全差距对应可执行测试,不以“注意安全”作为验收。
- 本阶段未修改生产代码、未切换端口、未接触真实写操作。