Files
multi-simadmin/docs/product/current-system-audit.md
T

196 lines
17 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.
# Multi SimAdmin 现状审计与迁移清单
> 审计对象:Phase 0.1 时仓库当前实现<br>
> 产品对照基线:API-first Multi SimAdmin V1<br>
> 上游证据基线:`3899/SimAdmin@58e220411d6599609f0eeda01eb7016e9212f970`
> Phase 0.1 文档导航:[返回项目章程](./project-charter.md)**现状审计**(本文)|[角色与流程](./personas-and-workflows.md)[信息架构](./information-architecture.md)
## 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 snapshotrevision;密码 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/actorR3 与批量 R2 全部进入 Job/auditR2 默认进入,仅 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` 的 catalog7 个分组,并再次硬编码 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 originloopback/RFC1918 也只限精确 authority;地址/DNS/redirect/proxy/Host/SNI 的完整允许与拒绝政策以[章程 7.5](./project-charter.md#75-已登记实例-origin-与-ssrflan-授权)为准 |
| 每实例 CookieJar | adapter client/session registry | Cookie 不跨实例;logout/reconcile/delete 清理会话;Cookie 不发给浏览器 |
| 密码登录与临时凭据 | 专用会话 BFF + SecretStore | health/auth-status 严格 R0verify/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 唯一规范](./project-charter.md#73-确认令牌唯一规范),包括 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 | 按模块结构化读页和写表单 | 不要求手写路径/JSONunsupported/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 目标模型
每个采集项必须包含:
```text
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](./project-charter.md#76-三个正交页面状态);领域/API 枚举统一为 `fresh`(中文“最新”),禁止 `latest`
Job 使用以下状态机(Preparation 独立,不是 Job):
```text
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](./project-charter.md#74-preparationjob-与-attempt-固定模型)为准。
## 5. 安全与可靠性差距
| 领域 | 已有基础 | 必补缺口 | 验收 |
|---|---|---|---|
| SSRF/URL | 当前 core/schema 有校验思想 | 已登记 origin 精确授权;只允许 http(s);规范 IPloopback/RFC1918 精确端口;拒绝 link-local/metadata/公网/转换地址;全地址 DNS 固定与 peer 复验;redirect 同 origin;忽略代理;Host/SNI 一致 | [章程 7.5](./project-charter.md#75-已登记实例-origin-与-ssrflan-授权)矩阵逐类通过,拒绝 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 全部 JobR2 默认 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. 迁移顺序与护栏
1. **Phase 0** 冻结文档、上游 operation 证据、脱敏 fixtures;生产代码不动,规格与质量复审通过前不提交。
2. **Phase 14** 新 monorepo/API/数据/adapter/status 与旧服务并存;禁止直接替换 8788。
3. **Phase 57** 新 Web、模块、Jobs、Audit 只调用 `/api/v1`;不从旧通用代理新增功能。
4. **Phase 8** 真实浏览器、真实实例、安全和独立复审;blocker/major 必须修复。
5. **Phase 9** 独立测试端口运行;导入旧配置预览;只读 shadow comparison;备份/回滚演练;最后切换。
6. 稳定窗口后才删除旧 `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。
- [ ] 安全差距对应可执行测试,不以“注意安全”作为验收。
- [ ] 本阶段未修改生产代码、未切换端口、未接触真实写操作。