# 模型制品页面安装与卸载功能设计

## 1. 建设目标

模型制品安装与卸载功能用于在模型管理页面完成模型文件从上传、校验、入库、安装、加载、停用、卸载到回收的全生命周期管理，使本地调试环境和服务器生产环境采用一致的操作模型。

建设目标如下：

1. 在页面查看五类模型的制品组成、安装位置、校验结果、加载状态和运行引用。
2. 支持浏览器分片上传、从制品库选择、从受控对象存储地址导入三种安装来源。
3. 安装前完成文件完整性、ONNX 可加载性、输入输出契约、标签和运行环境兼容性校验。
4. 安装过程异步执行并实时展示每个处理阶段、进度、日志和失败原因。
5. 安装成功后触发运行时刷新、模型预热和运行就绪检查，不要求人工重启整个系统。
6. 严格区分释放内存、卸载运行节点制品和永久删除制品库文件。
7. 禁止卸载正在生效、正在执行任务或仍被回滚策略引用的制品。
8. 支持失败回滚、重新安装、版本切换、多节点一致性和完整审计。

## 2. 范围与状态解释

### 2.1 管理对象

| 模型组 | 制品组成 | 安装要求 |
| --- | --- | --- |
| `vision-detector` | 主 ONNX、标签文件、清单 | 主模型必需，标签按模型契约要求提供 |
| `vision-segmenter` | 主 ONNX、标签或调色板、清单 | 主模型必需 |
| `change-detector` | 主 ONNX、预处理配置、清单 | 主模型必需 |
| `thermal-analyzer` | PP-YOLOE 主 ONNX、PatchCore 辅助 ONNX、标签文件、清单 | 主模型和辅助模型必须同时就绪 |
| `pointcloud-analyzer` | Open3D 几何分析运行库 | 属于镜像内置能力，不上传独立模型制品 |

Open3D 行显示“内置组件”，不提供模型安装或卸载按钮。Open3D 版本升级通过发布新的空间分析服务镜像完成。

### 2.2 状态含义

页面中的“服务连接”“制品状态”和“加载状态”是三个独立维度：

| 状态域 | 示例 | 含义 |
| --- | --- | --- |
| 服务连接 | 已连接、不可连接 | 健康接口是否可访问 |
| 制品状态 | 待安装、安装中、已就绪、安装失败 | 模型文件是否已安装到目标运行环境并通过校验 |
| 加载状态 | 未加载、加载中、已加载、加载失败 | 制品是否已进入推理进程内存或显存 |

服务已连接但制品待安装是正常状态，表示推理服务进程存在，但目标模型文件尚未安装。`1/5` 制品就绪表示当前只有内置的 Open3D 能力满足运行要求。

### 2.3 操作边界

| 操作 | 影响范围 | 文件是否保留 | 使用场景 |
| --- | --- | --- | --- |
| 释放模型 | 推理进程内存或显存 | 保留 | 释放计算资源，后续可按需重新加载 |
| 卸载制品 | 指定运行部署或节点 | 制品库原件保留 | 下线某个运行环境中的模型副本 |
| 删除制品 | MinIO/S3 制品库 | 不保留或进入回收站 | 清理错误上传、废弃版本或过期制品 |

模型运行页面提供“释放模型”和“卸载制品”。永久删除只能在“版本与制品”页面执行，并采用独立权限、审批和保留期。

## 3. 总体架构

采用控制面与执行面分离的方式。浏览器和 Spring Boot 后端不直接写入推理容器目录，制品安装由具备受控写权限的制品安装代理执行。

```mermaid
flowchart LR
    UI["模型管理页面"] --> API["Spring Boot 制品生命周期 API"]
    API --> DB[("PostgreSQL")]
    API --> S3["MinIO / S3 制品库"]
    API --> K["Kafka 安装任务事件"]
    K --> W["Artifact Installer 安装代理"]
    W --> S3
    W --> VOL["版本化模型数据卷"]
    VOL --> V["视觉推理服务（只读）"]
    VOL --> P["空间分析服务（只读）"]
    W --> V
    W --> P
    V --> API
    P --> API
    API --> SSE["SSE 进度事件"]
    SSE --> UI
```

### 3.1 控制面

- `ModelArtifactController`：提供上传、校验、安装、卸载和任务查询接口。
- `ModelArtifactLifecycleService`：负责状态机、权限校验、引用检查、幂等控制和审计。
- `ModelArtifactStorageService`：复用 MinIO 客户端，管理临时区、发布区和回收站。
- `ModelArtifactJobPublisher`：向 Kafka 发布安装、释放和卸载任务。
- `ModelArtifactEventService`：持久化事件并通过 SSE 推送页面。
- `ModelRuntimeDeploymentService`：安装完成后执行运行检查和运行配置切换约束。

### 3.2 执行面

新增 `artifact-installer` 服务：

- 在本地 Docker Compose 和服务器部署中均使用同一安装协议。
- 对模型数据卷具有读写权限；推理容器仍保持只读挂载。
- 从 MinIO/S3 拉取文件，不接受浏览器直接写入宿主机路径。
- 在隔离临时目录中校验、解压、生成清单并原子发布。
- 通过内部管理接口通知推理服务刷新注册表、加载或释放模型。
- 定期上报节点磁盘、制品清单和孤立目录。

### 3.3 运行时内部接口

内部接口仅允许后端和安装代理通过内网及服务身份调用，不由前端直接访问。

| 方法 | 路径 | 用途 |
| --- | --- | --- |
| POST | `/internal/v1/runtime/registry/refresh` | 重新读取版本化注册表 |
| POST | `/internal/v1/runtime/models/load` | 加载指定模型并执行预热 |
| POST | `/internal/v1/runtime/models/unload` | 从内存或显存释放指定模型 |
| GET | `/internal/v1/runtime/models/{group}/{version}` | 查询加载、会话和引用状态 |
| POST | `/internal/v1/runtime/models/probe` | 使用固定样本执行最小推理检查 |

内部接口使用短期服务令牌或双向 TLS，操作请求必须携带 `job_id`、`trace_id` 和幂等键。

## 4. 页面功能设计

### 4.1 系统运维页面

“系统运维 -> 模型运行时”表格增加操作列：

| 当前状态 | 主操作 | 次操作 |
| --- | --- | --- |
| 待安装 | 安装 | 查看要求 |
| 上传中或安装中 | 查看进度 | 取消任务 |
| 已就绪、未加载 | 加载 | 卸载制品 |
| 已就绪、已加载 | 释放模型 | 制品详情 |
| 安装失败 | 重试 | 查看日志、重新选择制品 |
| 内置组件 | 查看运行库 | 无 |

页面顶部显示以下汇总指标：

- 制品就绪数量。
- 正在安装数量。
- 安装失败数量。
- 已加载模型数量。
- 制品库占用和运行节点磁盘占用。

### 4.2 模型详情页面

“模型管理 -> 模型详情 -> 运行配置”保留连接检查和运行检查，并增加：

- 当前安装制品：版本、制品编号、SHA-256、安装时间和目标环境。
- 制品组件：主模型、辅助模型、标签和清单的独立状态。
- 运行节点：每个节点的安装、加载、预热和健康状态。
- 操作：安装、重新安装、加载、释放模型、卸载制品、查看任务。
- 引用：生效运行配置、运行中任务、发布记录和回滚保留状态。

### 4.3 安装向导

安装采用五步抽屉，不使用单个上传按钮直接发布。

#### 第一步：选择目标

- 模型组只读展示。
- 选择模型版本。
- 选择运行配置和目标环境。
- 显示运行档案、执行提供程序、设备、精度和剩余磁盘空间。
- 生产环境显示发布审批单和变更窗口。

#### 第二步：选择来源

支持三种来源：

1. 浏览器上传：适合本地调试和小规模运维。
2. 制品库选择：选择已经验证的 `model_artifacts` 记录。
3. 对象存储导入：填写受信任的 `s3://` 或 MinIO 对象地址及 SHA-256。

生产环境默认只允许“制品库选择”，上传或外部导入必须由模型发布管理员执行。

#### 第三步：上传组件

页面根据模型组动态生成槽位：

| 槽位 | 文件类型 | 必需性 |
| --- | --- | --- |
| 主模型 | `.onnx`、`.engine` 或约定包格式 | 除 Open3D 外必需 |
| 辅助模型 | `.onnx` | 红外 PatchCore 必需 |
| 标签 | `.txt` 或 `.json` | 按模型契约 |
| 配置 | `.json` 或 `.yaml` | 可选或按版本要求必需 |
| 清单 | `manifest.json` | 页面自动生成，可导入已有清单 |

上传规则：

- 默认分片大小 8 MB，支持断点续传、暂停和恢复。
- 客户端计算快速摘要，服务端完成最终 SHA-256 校验。
- 默认单文件上限 10 GB，可通过环境配置调整。
- 同一上传会话允许主模型、辅助模型和标签并行上传。
- 页面显示文件名、大小、摘要、上传速度、剩余时间和分片重试次数。

#### 第四步：校验确认

页面展示校验矩阵：

- 文件大小与 SHA-256。
- ONNX Protobuf 结构和 Opset。
- 输入名称、形状、数据类型和动态维度。
- 输出名称、形状和后处理契约。
- 标签数量与模型输出类别数。
- 模型组和版本一致性。
- CPU、CUDA 或 TensorRT 执行提供程序兼容性。
- 主辅模型组合完整性。
- 固定探针样本的会话创建和最小推理结果。
- 制品是否已存在以及是否允许复用。

阻断项必须全部通过。警告项需要填写确认原因，生产环境由审批人确认。

#### 第五步：安装与预热

安装进度采用固定阶段展示：

1. 创建安装任务。
2. 下载或汇集制品。
3. 校验摘要与清单。
4. 运行环境兼容性检查。
5. 写入隔离临时目录。
6. 原子发布版本目录。
7. 刷新运行时注册表。
8. 加载模型并执行预热。
9. 执行运行就绪检查。
10. 完成安装或自动回滚。

每个阶段展示状态、开始时间、耗时、处理节点、摘要日志和错误码。页面关闭后任务继续执行，再次进入可恢复进度视图。

### 4.4 卸载确认

点击“卸载制品”后，页面先调用影响分析接口，展示：

- 是否为当前生效运行配置。
- 是否存在运行中或排队任务。
- 是否被发布记录、回归基线或回滚策略引用。
- 当前加载会话和活动请求数量。
- 目标节点数量及各节点状态。
- 卸载后可释放的磁盘空间。
- MinIO 制品库原件是否保留。

存在阻断引用时不显示确认按钮，而是提供“查看引用”“切换运行配置”和“等待任务结束”入口。允许卸载时，需要输入模型版本确认；生产环境还需要有效审批单。

### 4.5 制品详情与任务中心

制品详情抽屉包含：

- 制品编号、模型版本、组件清单和存储地址。
- SHA-256、文件大小、创建者、创建时间和来源。
- 校验报告、运行时约束和固定探针结果。
- 已安装环境和节点。
- 当前引用和最近操作记录。
- 可用操作：安装到其他环境、重新校验、下载清单、进入回收站。

任务中心统一展示安装、重新安装、加载、释放、卸载和删除任务，支持按模型组、环境、状态和时间过滤。

## 5. 生命周期状态机

### 5.1 制品库状态

```mermaid
stateDiagram-v2
    [*] --> uploading: 创建上传会话
    uploading --> uploaded: 分片合并完成
    uploaded --> validating: 开始校验
    validating --> validated: 校验通过
    validating --> validation_failed: 校验失败
    validation_failed --> validating: 修正后重试
    validated --> released: 发布到制品库
    released --> deprecated: 标记废弃
    deprecated --> recycled: 进入回收站
    recycled --> released: 保留期内恢复
    recycled --> deleted: 保留期结束且无引用
```

### 5.2 运行环境安装状态

```mermaid
stateDiagram-v2
    [*] --> not_installed
    not_installed --> queued: 提交安装
    queued --> installing: 安装代理领取
    installing --> validating: 节点侧校验
    validating --> installed: 原子发布完成
    installed --> loading: 加载或首次推理
    loading --> ready: 预热通过
    loading --> load_failed: 加载失败
    installing --> install_failed: 安装失败
    validating --> install_failed: 校验失败
    install_failed --> queued: 重试
    load_failed --> loading: 重试加载
    ready --> unloading: 卸载申请通过
    installed --> unloading: 卸载申请通过
    unloading --> not_installed: 文件清理完成
```

所有状态转换由后端服务执行，前端不能直接提交目标状态。

## 6. 数据模型设计

### 6.1 扩展 `model_artifacts`

保留现有表并增加制品包级信息：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `package_format` | varchar(32) | onnx-package、tensorrt-package、builtin |
| `manifest` | jsonb | 组件、输入输出、标签、预处理和后处理契约 |
| `source_type` | varchar(32) | upload、registry、object_storage、pipeline |
| `lifecycle_status` | varchar(32) | uploading、validated、released、deprecated、recycled、deleted |
| `created_by` | varchar(128) | 创建人 |
| `released_at` | timestamptz | 发布到制品库时间 |
| `recycled_at` | timestamptz | 进入回收站时间 |

### 6.2 `model_artifact_files`

记录一个制品包中的所有文件：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | varchar(64) | 文件编号 |
| `artifact_id` | varchar(64) | 制品包编号 |
| `component_type` | varchar(32) | primary、secondary、labels、config、manifest |
| `file_name` | varchar(512) | 原始文件名 |
| `object_key` | text | MinIO/S3 对象键 |
| `sha256` | char(64) | 文件摘要 |
| `size_bytes` | bigint | 文件大小 |
| `content_type` | varchar(128) | 内容类型 |
| `validation_status` | varchar(32) | 文件校验状态 |
| `validation_report` | jsonb | 文件级校验报告 |

唯一约束为 `artifact_id + component_type + file_name`。

### 6.3 `model_artifact_upload_sessions`

复用测试文件分片上传模式，但模型制品使用独立表和独立 MinIO 前缀，避免测试临时文件生命周期误删模型文件。

主要字段包括会话编号、模型组、模型版本、状态、总文件数、总大小、分片大小、对象前缀、创建人、过期时间和完成时间。

### 6.4 `model_artifact_installations`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | varchar(64) | 安装记录编号 |
| `artifact_id` | varchar(64) | 制品包编号 |
| `runtime_deployment_id` | varchar(64) | 目标运行配置 |
| `runtime_node_id` | varchar(128) | 目标节点或共享卷编号 |
| `status` | varchar(32) | 安装生命周期状态 |
| `install_path` | text | 版本化安装目录 |
| `installed_manifest` | jsonb | 节点实际文件与摘要 |
| `loaded` | boolean | 是否已加载 |
| `active_request_count` | int | 最近一次上报的活动请求数 |
| `installed_at` | timestamptz | 安装时间 |
| `loaded_at` | timestamptz | 加载时间 |
| `last_verified_at` | timestamptz | 最近校验时间 |
| `error_code` | varchar(64) | 最近错误码 |
| `error_detail` | jsonb | 最近错误详情 |

唯一约束为 `artifact_id + runtime_deployment_id + runtime_node_id`。

### 6.5 `model_artifact_jobs` 与 `model_artifact_job_events`

任务表保存任务类型、目标、状态、进度、当前阶段、幂等键、审批单、操作者、追踪编号、错误信息和开始结束时间。事件表按任务保存递增序号、阶段、状态、进度、消息和扩展载荷，用于断线续看和审计。

### 6.6 运行配置关联

`model_runtime_deployments` 增加：

- `desired_artifact_id`：配置期望安装的制品。
- `installed_artifact_id`：当前完成安装并通过预热的制品。
- `artifact_sync_status`：期望与实际是否一致。

只有 `desired_artifact_id = installed_artifact_id` 且所有必需节点就绪时，运行检查才返回 `healthy`。

## 7. 制品清单契约

每个制品包必须生成 `manifest.json`：

```json
{
  "schema_version": "1.0",
  "model_group": "thermal-analyzer",
  "model_version": "cpu-v1.0.0",
  "runtime": "onnxruntime-cpu",
  "precision": "FP32",
  "components": [
    {
      "type": "primary",
      "file": "ppyoloe-s/model.onnx",
      "sha256": "<64位摘要>",
      "required": true
    },
    {
      "type": "secondary",
      "file": "patchcore/model.onnx",
      "sha256": "<64位摘要>",
      "required": true
    },
    {
      "type": "labels",
      "file": "ppyoloe-s/labels.txt",
      "sha256": "<64位摘要>",
      "required": true
    }
  ],
  "inputs": [{ "name": "images", "dtype": "float32", "shape": [1, 3, 512, 512] }],
  "output_contract_version": "1.0",
  "preprocess": { "color_order": "RGB", "scale": 0.0039215686 },
  "created_at": "2026-07-22T00:00:00Z"
}
```

清单由服务端生成或验证。对象地址、密钥和宿主机绝对路径不得写入清单。

## 8. 安装流程详细设计

```mermaid
sequenceDiagram
    actor U as 模型运维人员
    participant UI as 管理页面
    participant API as Spring Boot
    participant S3 as MinIO/S3
    participant K as Kafka
    participant W as 安装代理
    participant R as 推理服务

    U->>UI: 选择版本、环境和制品来源
    UI->>API: 创建上传会话
    UI->>API: 分片上传组件
    API->>S3: 写入 staging 前缀
    UI->>API: 完成上传并请求校验
    API->>S3: 合并并校验 SHA-256
    API-->>UI: 返回校验矩阵
    U->>UI: 确认安装
    UI->>API: 创建安装任务（幂等键）
    API->>K: artifact.install.requested
    K->>W: 领取任务
    W->>S3: 下载到隔离目录
    W->>W: 契约、运行时和摘要校验
    W->>W: 原子发布版本目录
    W->>R: 刷新注册表并加载预热
    R-->>W: 返回加载与探针结果
    W->>K: artifact.install.completed
    K->>API: 更新安装、任务和审计状态
    API-->>UI: SSE 推送完成事件
```

### 8.1 原子发布

1. 安装代理写入 `<model-root>/.staging/<job-id>/`。
2. 校验每个文件摘要与清单一致。
3. 目标目录采用 `<model-group>/<version>/<artifact-sha>/`，目录不可变。
4. 完成后使用同一文件系统内的原子重命名发布目录。
5. 更新版本注册表临时文件并原子替换正式文件。
6. 推理服务刷新注册表；旧模型会话继续处理已进入的请求。
7. 新模型预热失败时恢复原注册表，失败目录保留到诊断期限后清理。

### 8.2 幂等和并发

- 创建任务必须提供 `Idempotency-Key`。
- 同一运行配置和模型组同一时间只允许一个安装或卸载任务。
- 相同 SHA-256 已存在时复用制品库对象，不重复上传。
- 相同版本和摘要已安装时返回现有安装记录，不重复写盘。
- 多节点安装采用逐节点状态；默认全部节点成功后才标记整体就绪。
- 生产可配置滚动安装，每批节点预热通过后再继续下一批。

## 9. 释放与卸载流程详细设计

### 9.1 释放模型

释放只回收内存或显存：

1. 检查模型是否允许按需释放。
2. 将模型标记为 `draining`，停止接收新请求。
3. 等待活动请求归零；超过超时则取消释放，不中断请求。
4. 关闭 ONNX Runtime、TensorRT 或其他推理会话。
5. 更新加载状态和资源指标。
6. 制品文件保持不变，下一次请求可按策略重新加载。

### 9.2 卸载制品

```mermaid
sequenceDiagram
    actor U as 模型运维人员
    participant UI as 管理页面
    participant API as Spring Boot
    participant DB as PostgreSQL
    participant W as 安装代理
    participant R as 推理服务

    U->>UI: 点击卸载制品
    UI->>API: 查询卸载影响
    API->>DB: 检查运行配置、任务、发布和回滚引用
    API-->>UI: 返回阻断项与影响范围
    U->>UI: 输入版本并确认
    UI->>API: 创建卸载任务
    API->>W: 发布卸载任务
    W->>R: 进入 draining 并释放模型
    R-->>W: 活动请求归零
    W->>W: 删除目标版本目录并刷新注册表
    W-->>API: 返回节点卸载结果
    API->>DB: 更新安装记录、就绪状态和审计日志
    API-->>UI: 推送完成事件
```

卸载默认只删除目标运行环境中的副本，MinIO/S3 制品库原件继续保留，以便重新安装和回滚。

### 9.3 卸载阻断条件

满足任一条件时禁止卸载：

- 制品属于当前生效运行配置。
- 存在运行中、排队或重试中的模型测试或业务推理任务。
- 仍有活动请求或模型会话无法安全释放。
- 制品被当前生产发布、候选发布、回归基线或回滚窗口引用。
- 多节点状态不一致且影响分析未完成。
- 安装、加载、切换或其他卸载任务正在执行。
- 当前操作者没有目标环境的卸载权限。

### 9.4 制品库删除

永久删除采用两阶段处理：

1. 进入回收站：对象移动到 `recycle/` 前缀，默认保留 30 天。
2. 永久清理：保留期结束、引用数为零且审批通过后由生命周期任务删除。

删除前再次计算引用，不信任页面确认时的旧快照。制品摘要、清单、审计记录和删除证明长期保留，但模型二进制按策略清除。

## 10. 外部接口设计

### 10.1 查询与影响分析

| 方法 | 路径 | 用途 |
| --- | --- | --- |
| GET | `/api/v1/model-artifacts/runtime-overview` | 查询五类模型安装与加载汇总 |
| GET | `/api/v1/model-versions/{versionId}/artifacts` | 查询版本制品包 |
| GET | `/api/v1/model-artifacts/{artifactId}` | 查询清单、校验、安装和引用详情 |
| GET | `/api/v1/model-runtime-deployments/{deploymentId}/installations` | 查询目标运行配置的安装记录 |
| GET | `/api/v1/model-artifact-installations/{installationId}/uninstall-impact` | 查询卸载影响与阻断项 |

### 10.2 上传与校验

| 方法 | 路径 | 用途 |
| --- | --- | --- |
| POST | `/api/v1/model-artifact-upload-sessions` | 创建上传会话 |
| POST | `/api/v1/model-artifact-upload-sessions/{sessionId}/files` | 登记文件和分片计划 |
| PUT | `/api/v1/model-artifact-upload-files/{fileId}/parts/{partNo}` | 上传分片 |
| POST | `/api/v1/model-artifact-upload-files/{fileId}/complete` | 合并文件并校验摘要 |
| POST | `/api/v1/model-artifact-upload-sessions/{sessionId}/complete` | 完成制品包并生成清单 |
| POST | `/api/v1/model-artifacts/{artifactId}/validate` | 执行完整制品校验 |

### 10.3 安装、加载与卸载

| 方法 | 路径 | 用途 |
| --- | --- | --- |
| POST | `/api/v1/model-runtime-deployments/{deploymentId}/installations` | 创建安装任务 |
| POST | `/api/v1/model-artifact-installations/{installationId}/load` | 加载并预热模型 |
| POST | `/api/v1/model-artifact-installations/{installationId}/unload` | 仅释放内存或显存 |
| DELETE | `/api/v1/model-artifact-installations/{installationId}` | 从目标环境卸载制品 |
| POST | `/api/v1/model-artifact-jobs/{jobId}/retry` | 重试失败任务 |
| POST | `/api/v1/model-artifact-jobs/{jobId}/cancel` | 取消尚未原子发布的任务 |
| GET | `/api/v1/model-artifact-jobs/{jobId}` | 查询任务详情 |
| GET | `/api/v1/model-artifact-jobs/{jobId}/events` | 查询历史事件 |
| GET | `/api/v1/model-artifact-jobs/{jobId}/stream` | SSE 订阅实时事件 |

### 10.4 安装请求示例

```json
{
  "artifact_id": "artifact-thermal-cpu100-a1",
  "target_nodes": ["cpu-local"],
  "load_after_install": true,
  "warmup_profile": "default",
  "approval_ticket": null,
  "reason": "安装本地红外分析模型",
  "operator": "模型运维管理员"
}
```

### 10.5 卸载影响响应示例

```json
{
  "allowed": false,
  "installation_id": "install-thermal-cpu100-a1",
  "active_deployment": true,
  "active_requests": 0,
  "estimated_reclaim_bytes": 1623456789,
  "blocking_references": [
    {
      "type": "runtime_deployment",
      "id": "runtime-thermal-cpu",
      "message": "当前生效运行配置正在使用该制品"
    }
  ],
  "required_actions": ["switch_runtime_deployment"]
}
```

## 11. 事件设计

Kafka 主题：

| 主题 | 生产者 | 消费者 |
| --- | --- | --- |
| `model.artifact.install.requested` | Spring Boot | 安装代理 |
| `model.artifact.uninstall.requested` | Spring Boot | 安装代理 |
| `model.artifact.load.requested` | Spring Boot | 安装代理 |
| `model.artifact.job.progress` | 安装代理 | Spring Boot |
| `model.artifact.job.completed` | 安装代理 | Spring Boot |
| `model.artifact.job.failed` | 安装代理 | Spring Boot、告警模块 |
| `model.runtime.registry.changed` | 安装代理 | 推理服务、监控模块 |

事件必须包含 `event_id`、`job_id`、`trace_id`、`artifact_id`、`deployment_id`、`node_id`、时间和状态版本。消费者按 `event_id` 去重，并使用状态版本拒绝旧事件覆盖新状态。

## 12. 存储布局

### 12.1 MinIO/S3

```text
rail-model-artifacts/
  staging/<upload-session-id>/...
  released/<model-group>/<model-version>/<artifact-sha>/...
  reports/<artifact-id>/validation.json
  recycle/<recycle-date>/<artifact-id>/...
```

`staging/` 使用短期生命周期策略；`released/` 不允许覆盖同名对象；`recycle/` 使用保留期和独立删除权限。

### 12.2 运行节点

```text
<model-root>/
  .staging/<job-id>/...
  packages/<model-group>/<model-version>/<artifact-sha>/...
  registry/models.json
  quarantine/<job-id>/...
```

推理服务只读挂载 `packages/` 和 `registry/`。安装代理读写整个模型根目录。校验失败文件移动到 `quarantine/`，超过诊断保留期后自动清理。

## 13. 权限与审批

| 角色 | 本地安装 | 生产安装 | 释放模型 | 卸载制品 | 永久删除 |
| --- | --- | --- | --- | --- | --- |
| 查看人员 | 否 | 否 | 否 | 否 | 否 |
| 模型运维人员 | 是 | 提交申请 | 是 | 非生产环境 | 否 |
| 模型发布管理员 | 是 | 审批并执行 | 是 | 是 | 提交申请 |
| 安全管理员 | 否 | 审批高风险变更 | 否 | 审批 | 审批并执行 |
| 审计人员 | 只读 | 只读 | 只读 | 只读 | 只读 |

安全要求：

- 页面不接收宿主机任意路径，防止路径穿越和越权读取。
- 导入地址只允许配置的 MinIO/S3 桶和 HTTPS 域名白名单。
- 上传文件不以原始文件名作为存储路径。
- 制品目录禁止执行权限，安装代理以非 root 用户运行。
- 压缩包校验解压后总大小、文件数量、软链接和路径穿越。
- 生产任务必须绑定审批单、操作者和变更原因。
- 所有安装、校验、加载、释放、卸载、恢复和删除操作写入 `model_audit_logs`。
- 页面只展示脱敏日志，不返回对象存储凭据、内部令牌或宿主机敏感路径。

## 14. 异常处理与回滚

| 异常 | 处理方式 |
| --- | --- |
| 上传中断 | 保留已上传分片，允许在会话有效期内恢复 |
| SHA-256 不一致 | 标记文件校验失败，禁止生成制品包 |
| ONNX 无法加载 | 保留校验报告，禁止安装 |
| 节点磁盘不足 | 安装前阻断，不进入写盘阶段 |
| 安装代理离线 | 任务保持排队并告警，不修改运行配置 |
| 原子发布前失败 | 删除或隔离临时目录，现有版本不受影响 |
| 注册表刷新失败 | 恢复旧注册表并标记安装失败 |
| 预热失败 | 卸载新会话、恢复旧注册表和旧模型会话 |
| 多节点部分成功 | 停止后续批次；按策略回滚已成功节点或保持隔离状态 |
| 卸载等待超时 | 取消卸载，保持文件和注册表不变 |
| 事件重复或乱序 | 按事件编号去重并比较状态版本 |

安装任务失败后不得仅修改数据库状态伪造成功；最终状态必须由安装代理的实际清单和运行时探针共同确认。

## 15. 监控与审计

Prometheus 指标：

- `model_artifact_job_total{type,status,environment}`
- `model_artifact_job_duration_seconds{type,stage}`
- `model_artifact_upload_bytes_total`
- `model_artifact_validation_failure_total{reason}`
- `model_artifact_installed_bytes{node,model_group}`
- `model_runtime_loaded_models{node,provider}`
- `model_runtime_model_load_seconds{model_group,version}`
- `model_artifact_orphan_directory_total{node}`
- `model_artifact_replica_drift_total{deployment}`

告警条件：

- 安装任务超过配置时限仍未完成。
- 生产节点制品摘要不一致。
- 运行配置期望制品与实际制品不一致。
- 预热连续失败。
- 模型磁盘剩余空间低于阈值。
- 出现孤立目录、未登记制品或未授权卸载。

审计记录应支持按制品、模型版本、运行配置、任务、操作者和审批单查询，并保留操作前后快照。

## 16. 测试设计

### 16.1 单元测试

- 模型组必需组件规则，重点覆盖红外主辅模型组合和 Open3D 内置能力。
- 制品状态机合法与非法转换。
- SHA-256、清单、文件名和对象键校验。
- 卸载引用分析和权限判定。
- 幂等键、重复事件和乱序事件处理。
- 版本目录与路径穿越防护。

### 16.2 集成测试

- 浏览器分片上传、中断续传、重复分片和最终合并。
- MinIO 临时区到发布区的对象生命周期。
- Kafka 安装、进度、完成和失败事件闭环。
- 安装代理写入临时目录、原子发布和回滚。
- 推理服务注册表刷新、加载、预热、释放和重新加载。
- PostgreSQL 安装记录、任务事件和审计一致性。

### 16.3 端到端测试

1. 安装可见光 ONNX 并从“待安装”变为“已就绪”。
2. 安装红外主模型但缺少 PatchCore，必须阻断。
3. 安装摘要错误制品，必须在写入正式目录前失败。
4. 安装成功后运行检查必须返回 `healthy`。
5. 释放已加载模型后，制品保持“已就绪”，加载状态变为“未加载”。
6. 对生效配置执行卸载，页面必须显示阻断引用。
7. 切换运行配置并等待任务结束后，卸载成功且制品库原件保留。
8. 预热失败时旧模型仍可处理请求。
9. 多节点中一个节点失败时整体不得显示全部就绪。
10. 页面刷新和断线重连后能恢复任务进度。

### 16.4 安全测试

- 非法扩展名、伪造 MIME、超大文件和压缩炸弹。
- `../`、绝对路径、软链接和 Unicode 路径混淆。
- 越权安装、跨环境卸载、伪造审批单和重放请求。
- SSRF 地址、非白名单对象存储和凭据泄漏检查。
- 上传并发、磁盘耗尽和任务队列压力测试。

## 17. 验收标准

1. 页面能够对四类外部模型制品执行上传、校验、安装、加载、释放和安全卸载。
2. Open3D 正确显示为内置组件，不出现无意义的安装按钮。
3. 红外模型缺少任一必需组件时不能安装为就绪状态。
4. 安装任务每个阶段可视化，页面刷新后进度不丢失。
5. 安装成功后自动刷新运行时并执行预热，无需重启整套系统。
6. 生效模型、活动任务和回滚引用能够阻断卸载。
7. 卸载运行节点制品不删除 MinIO/S3 制品库原件。
8. 制品摘要、运行时实际文件和数据库记录保持一致。
9. 失败安装不影响当前生效模型，自动回滚结果可追溯。
10. 本地 CPU 与服务器 GPU 使用同一页面流程、状态机和接口契约。
11. 所有高风险操作经过权限校验、二次确认和审计记录。
12. 自动化测试覆盖正常流程、组合模型、并发、失败回滚和安全边界。

## 18. 实施顺序

### 第一阶段：制品库与本地安装

- 完成数据库扩展、模型制品上传、校验和详情页面。
- 增加本地 `artifact-installer` 服务和版本化模型目录。
- 完成安装、加载、释放、卸载及 SSE 进度。
- 将现有安装脚本保留为应急运维入口，并复用同一清单格式。

### 第二阶段：服务器与多节点

- 接入服务器 GPU 运行配置、生产审批和节点级状态。
- 支持滚动安装、多节点一致性校验、失败回滚和磁盘治理。
- 推理容器模型目录改为只读，安装代理独占写权限。

### 第三阶段：制品治理

- 完成回收站、保留期、永久删除、孤立文件巡检和容量报表。
- 接入发布记录、回归测试门禁和自动回滚策略。
- 完成 Prometheus 指标、告警规则和长期审计查询。

三个阶段共用同一数据模型和接口，不使用本地专用的临时页面逻辑。
