# AI 工具应用制品接口

实现基于现有 Gin、PostgreSQL、Redis、S3 分片上传和 software_releases 状态机。上传不会隐式发布，机器凭据也不是网页登录凭据。[OpenAPI](/help/machine-api) 与 Python CLI（仓库文件：../scripts/release-artifact.py） 可直接使用。

## 管理与身份

软件版本页的「AI 工具凭据」支持选择应用、授予操作范围、设置有效期、创建、吊销、轮换。需 `release-tokens:write`；迁移默认只授予 admin。该权限是全局管理权限，与现有 `releases:write` 的平台管理员模型一致，**本仓库没有应用所有者/租户模型**，不假定普通用户拥有某个应用。若后续引入租户，需要同时收紧两个管理权限。

凭据使用256位随机值 `acr_…`，数据库只存 SHA-256，创建/轮换仅返回一次原文；不存浏览器 localStorage，不写日志。轮换创建新记录并在同一事务吊销旧记录，保留范围和到期时间，最长90天。每次请求检查吊销/过期、应用启用、签发者账号有效且仍有 Token 管理权限。吊销不终止已进入执行的请求；已签发 S3 分片 URL 最长15分钟仍可使用，但不能绕过完成校验或发布授权。

机器请求使用 `Authorization: Bearer $AC_RELEASE_TOKEN`，仅 `/api/v1/automation/applications/…` 免 Cookie/CSRF，管理端仍需原 Cookie + Origin + CSRF。应用参数使用 UUID，不是公开 client_id。

| Scope | 能力 |
| --- | --- |
| releases:read | 查询该应用的机器版本、构建和上传状态 |
| releases:create | 创建该应用的草稿版本 |
| uploads:write | 创建/恢复上传、签发分片、上传小文件、校验完成、取消上传 |
| publish:development | alpha 通道发布/撤销 |
| publish:staging | beta 通道发布/撤销 |

稳定通道 stable 映射生产环境，**不提供生产机器发布 scope**。管理员在已有软件版本页查看 Git commit、构建元数据、安装包及摘要后，使用既有发布/定时发布确认流程完成生产发布。机器不可定时发布、删除版本或撤销生产发布。环境是现有分发通道映射，不代表私有测试环境；alpha/beta 发布后同样可通过公开更新接口下载。

## 契约与流程

返回沿用 `{code:0,message:"success",data:…}`；错误为 `{code:HTTP状态×100,message:…}`。400 输入/文件/校验失败，401 凭据无效，403 应用或 scope/生产保护拒绝，404 记录不存在，409 幂等冲突/版本 revision 冲突，429 频率或配额超限，503 存储、数据库、Redis或并发容量不可用。不回传内部异常或凭据。

前缀 `P=/api/v1/automation/applications/{application}/releases`：

| 请求 | 内容 / 返回 |
| --- | --- |
| POST P | 必须携带 Idempotency-Key；version、version_code、platform、arch、channel、git_commit、metadata（字符串字典）、notes；返回草稿 Release |
| GET P/{id} | release、assets、build、production_approval_required |
| POST P/{id}/uploads | filename、size、sha256（小写 hex）、revision；返回 asset、part_size、uploaded_parts |
| POST P/{id}/uploads/{asset}/file | multipart/form-data 唯一 file 字段，≤32MiB；只上传一个分片，仍须 complete |
| POST P/{id}/uploads/{asset}/part | part_number、sha256（32字节 Base64）；返回预签名 url、必须发送的 headers |
| POST P/{id}/uploads/{asset}/complete | 无正文；合并及同步校验，成功才将 asset 标记 ready，并递增 release.revision |
| DELETE P/{id}/uploads/{asset} | 取消未完成上传，后台重试清理 |
| POST P/{id}/state | action=publish/cancel、revision；受环境 scope 限制 |

管理员 API：`GET/POST /api/v1/admin/release-tokens`；`DELETE /:token`；`POST /:token/rotate`；`GET /audit?application_id=…&page=1`。列表也必须传 application_id。管理页面仍通过 `/admin/releases/:id/state` 发布/撤销，保留现有 revision 并发控制。Token 审计 API 只返回记录，不返回任何凭据。

创建幂等键为1–128位字母、数字、`.`、`_`、`-`；唯一范围是应用，跨 Token 和轮换有效。相同规范化 JSON 输入返回同一版本的**当前状态**，不同输入返回409；删除版本也保留键，不能复用它创建新版本。manifest 幂等不包含文件：上传恢复另按 filename/size/sha256 精确匹配；相同版本有不同文件的会话需先取消。完整上传重复 complete 不递增 revision。发布后先查询状态再重试，状态改变时旧 revision 返回409，避免误撤销或重复发布。

Git commit 必须是完整40或64位小写 hex；metadata 最多16KiB，仅字符串值，不得携带密钥。version_code 为1–9007199254740991，同应用/平台/架构/通道保持既有版本唯一约束。

## 文件策略、存储与资源界限

采用现有私有 S3 配置、AES-GCM 密钥加密、随机对象 key、分片恢复和清理 worker。机器 API 不接收任意存储地址或对象 key，不执行、部署或解压包。文件名只接受 ASCII 字母数字及 `._-`，白名单 `.zip .apk .exe .msi .dmg .deb .rpm .appimage`，并检查对应文件头/尾；不是病毒扫描或代码签名验证。

机器制品单个≤128MiB（同步校验界限）、小文件表单≤32MiB、每片32MiB、上传有效24小时。每个应用最多100个未发布/取消版本，机器上传按**该应用所有非 deleted 制品预留大小**计算2GiB配额，包括等待清理对象；应用行锁防止并发超额。既有纯管理员版本上传保持原20GiB规则，故这是机器入口配额，不是管理员全局硬上限。机器版本即使通过管理员上传也执行机器文件策略与完整摘要校验。

机器请求每 IP 每分钟240次、每 Token 每分钟120次（Redis），单进程最多8个并发请求。Redis 故障拒绝请求。同步校验流式读取 S3 对象写入操作系统临时目录，最多128MiB/请求，结束删除临时文件；部署应为临时目录提供容量和访问隔离。分片合并后由服务端重算完整 SHA-256，不能把 S3 metadata 或 multipart 组合校验和当作完整文件摘要。ZIP/APK 不解压，检查目录数量≤10000、声明展开量≤256MiB、异常压缩比例、绝对/父级/Windows路径、特殊文件/符号链接及嵌套常见压缩包。

校验失败不会 ready，也不会发布；已合并的错误对象需取消会话再重新上传，后台清理。网络中断时先 GET 状态：若已 ready 可继续，若 S3 已合并但 DB 未提交，重试 complete 通过 HEAD + GET 恢复。未校验失败对象仍计入配额，防止重试滥用。现有 worker 负责过期/取消对象清理，S3 必须设置终止过期 multipart 生命周期；对象存储版本控制/保留规则可能导致存储费用残留。

管理员配置 S3 的 IAM、Endpoint、Bucket、Region、CORS 与 DATA_KEY_FILE 沿用 [安装包存储说明](/help/releases)。新增开关 `MACHINE_RELEASES_ENABLED=false` 可关闭机器路由，管理吊销接口保留。当前资源阈值是源码中的固定安全上限；扩大文件限额需要异步校验队列、超时和磁盘预算设计，本次不提供无限制调大开关。生产网关必须使用 HTTPS，禁止记录 Authorization、表单内容和预签名 URL。

## 审计与回滚

迁移 `012_machine_releases.sql` 新增 `release_api_tokens`、`release_builds`、`release_machine_audit`，以及管理权限。迁移沿用自动排序、事务和 advisory lock，可重复执行。未修改原版本/制品状态约束。审计保留 Token ID、应用、路由模板、版本 ID、HTTP 结果；执行前先落 intent，outcome=0 表示中断/未知，需查最终状态。错误应用和 scope 尝试也被记录；无法认证的随机 Token 不持久化其原文/摘要，受 IP 限速。原 release 审计保留签发者身份，新审计补充实际 Token。审计表无公开写入入口；数据库管理员仍属于信任边界，生产应另行归档。

回滚可由相应环境管理员撤销当前版本，使尚未升级的客户端重新看到旧的最高已发布版本。已经升级的客户端不会自动降级：应将已知良好构建作为**更高 version_code 的新版本**上传、校验和发布。不要先执行「清除历史版本」再计划回滚；被清理制品无法恢复。生产回滚同样须人工确认。

## AI 工具使用

Python≥3.11，无第三方依赖。从密钥管理器注入 `AC_RELEASE_TOKEN`，不要把 Token 放命令行、提交文件或 shell tracing 输出中。

```sh
python scripts/release-artifact.py --base-url https://auth.example.com \
  --application APPLICATION_UUID --file ./app.zip \
  --version 1.2.0 --version-code 102 --git-commit FULL_GIT_COMMIT \
  --platform windows --arch x64 --channel beta \
  --idempotency-key pipeline-102 --metadata '{"builder":"ai-tool","run":"102"}'
```

默认只创建并上传草稿，附加 `--publish` 发布 alpha/beta；stable 明确拒绝该参数。CLI 不跟随重定向，不把 API Token 发给 S3；可用同一键重新执行，已 ready 且摘要/文件名/大小匹配时跳过上传，否则恢复同一会话重新写入分片。不要在同一构建过程中改动文件。结果只打印版本 ID、状态、摘要及生产审批提示。

curl 小文件示例（已有 RELEASE_ID、ASSET_ID 来自上述 API，环境中已有 Token）：

```sh
curl --fail-with-body "$BASE/api/v1/automation/applications/$APP/releases/$RELEASE/uploads/$ASSET/file" \
  -H "Authorization: Bearer $AC_RELEASE_TOKEN" -F 'file=@app.zip'
curl --fail-with-body -X POST "$BASE/api/v1/automation/applications/$APP/releases/$RELEASE/uploads/$ASSET/complete" \
  -H "Authorization: Bearer $AC_RELEASE_TOKEN"
```

共享主机的 curl 命令参数可能被本机进程查看，推荐使用上面的 Python CLI；不得启用 `curl -v` 或命令跟踪输出密钥。

## 本地启动与验证

应用直接在本机运行。先检查 `docker ps` 与 `docker port Postgres18 5432/tcp`、`docker port RedisPwd 6379/tcp`；**不得运行 compose 或创建/重建数据库容器**。连接密码仅保存在被 Git 忽略的 `.local/database-connections.md`。从该文件在当前进程注入 DATABASE_URL（postgres 用户）和 REDIS_URL，使用检查到的 loopback 端口。

```powershell
# 设置当前进程的 DATABASE_URL、REDIS_URL，以及本机密钥路径后：
$env:APP_ENV='development'
$env:APP_ORIGIN='http://localhost:13140'
$env:LISTEN_ADDR='127.0.0.1:13141'
$env:JWT_PRIVATE_KEY_FILE='<本机JWT私钥路径>'
$env:DATA_KEY_FILE='<本机32字节数据密钥路径>'
cd server
go run ./cmd/server keygen  # 仅新环境，已有密钥不会覆盖；两个父目录须已存在
go run ./cmd/server migrate
go run ./cmd/server
# 另一个本机终端：cd web; npm ci; npm run dev
```

迁移生产/已有业务库前按惯例备份；本次仅在测试专用随机 schema 应用迁移，没有修改用户业务表。

```powershell
# 脚本读取忽略目录的凭据表，检查既有容器状态和端口，不打印连接密码。
./scripts/test-machine-releases.ps1 -Go '<go.exe路径>'
./scripts/test-machine-releases.ps1 -Go '<go.exe路径>' -Run '.'
cd server; go vet ./...
cd ../web; npm run typecheck
python ../scripts/release-artifact.py --help
```

实际结果（2026-09-10）：Postgres18 / RedisPwd 均健康，loopback 5432 / 6379；MySQL 未使用。Go 全套执行通过（包含新增机器流程和现有数据库集成测试），只读页面测试因未配置 TEST_READONLY_DATABASE_URL 跳过。新流程覆盖成功 multipart/预签名上传、生产人工发布、状态/撤销、摘要错误、非法文件、应用/操作越权、重复与并发幂等、Token 失效及轮换、速率与草稿配额。Python CLI 通过实际本机 HTTP API 端到端上传、发布并重复执行，确认恢复同一版本。`go vet ./...`、Go 可执行文件构建、`npm run typecheck`、`npm run build -- --webpack`、Python 编译和帮助命令、`git diff --check` 均通过；OpenAPI YAML 成功解析，12个路径、61个本地引用均可解析。

S3 为本机 httptest 协议服务，不是实际云 Bucket；真实服务的 IAM、SigV4、CORS、跨网超时、大文件吞吐和对象生命周期尚未实测。浏览器人工/自动 UI 交互验收未执行，前端验证范围为类型检查和生产构建。可在配置现有 S3 后通过 CLI 复现完整流程，不需要新增数据库容器。npm 对当前本机 Node 预发布版本给出兼容性提示，但类型检查和 webpack 生产构建均成功；部署建议使用项目支持的稳定 Node 版本。

## 修改文件索引

| 文件 | 修改 |
| --- | --- |
| server/internal/app/machine_releases.go | Token、scope、幂等创建、归属、状态、审计与路由 |
| server/internal/app/machine_artifacts.go | 表单上传、文件策略、完整摘要和 ZIP 检查 |
| server/internal/app/app.go | 机器路由注册、CSRF边界和请求大小 |
| server/internal/app/release_uploads.go | 复用上传流程，加入机器配额和完成校验 |
| server/internal/app/releases.go | 管理列表返回构建来源 |
| server/cmd/server/main.go、.env.example | 机器 API 开关 |
| server/internal/database/migrations/012_machine_releases.sql | 三张表、索引和管理权限 |
| server/internal/app/machine_artifacts_test.go | 文件和 scope 边界单测 |
| server/internal/app/machine_releases_integration_test.go | PostgreSQL/Redis/S3 协议及 CLI 联调 |
| web/components/workspace/release-tokens.tsx | 管理 Token 界面 |
| web/components/workspace/releases.tsx | 管理入口及构建详情 |
| scripts/release-artifact.py | AI 工具 CLI |
| scripts/test-machine-releases.ps1 | 既有本地服务检测与测试启动 |
| docs/machine-releases.md、docs/machine-releases.openapi.yaml、docs/releases.md | 设计、契约、操作、安全和验证文档 |
