docs: 更新 MVP-1 管理端实现状态 (#24)

This commit is contained in:
ila
2026-08-22 11:14:03 +08:00
parent 4f265621c6
commit 48e9551d7b
7 changed files with 76 additions and 50 deletions
+6 -8
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Project-Profile
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Project-Profile.-
wiki_revision: c22b7d3cf529b87b30f02ee2ec4cedbf9eb679d8
synchronized_at: 2026-08-22T02:09:31Z
wiki_revision: 73e341c9849cdbb69fb9eacb496e72928508fe5c
synchronized_at: 2026-08-22T03:08:12Z
<!-- gitea-wiki-mirror:end -->
# 项目档案
@@ -148,8 +148,8 @@ synchronized_at: 2026-08-22T02:09:31Z
- 核心 Wiki 同步配置是 `wiki-docs.json`;Gitea 地址可用 `GITEA_URL` 覆盖。
- Gitea PAT 仅从进程环境、MCP 安全配置或本机未跟踪的环境文件加载,不写入仓库、工单或 Wiki。
- 数据库连接串由 `CHORUS_DSN` 提供。
- Provider `api_key` 使用 AES-GCM 加密落库;`api_key_enc` 采用带 `version`、非敏感 `key_id`、nonce 和 ciphertext 的版本化信封,主密钥从环境/密钥管理系统取得且不入库。轮换按“新 key_id 写入、后台重加密、验证后退役旧密钥”执行,不原地猜测密钥版本。
- 会话签名/加密密钥与 Provider 主密钥分离,生产 Cookie 必须启用 `Secure`、`HttpOnly` 和合适的 `SameSite`。
- Provider `api_key` 按用户已接受的风险决策明文保存在 `provider_credentials.api_key`;列表和普通详情只返回是否已配置,只有受 JWT/Casbin 保护的单 Provider 凭据接口可显式回显,且响应设置 `Cache-Control: no-store` 与 `Pragma: no-cache`。
- 会话签名/加密密钥仍由受保护配置提供;生产 Cookie 必须启用 `Secure`、`HttpOnly` 和合适的 `SameSite`。Provider 明文密钥不得进入代码、日志、审计、错误、工单、Wiki、原型或截图。
- 生成物默认落受保护的本地目录,不能直接把真实路径暴露为公开静态 URL;访问先校验用户归属。storage 从第一天是接口,预留 S3/OSS。
- 测试只用构造数据与 mock 上游;真实连通性检查必须是管理员明确触发的单次低成本动作,并有审计和冷却。
- 上传数量、单文件/总大小、允许 MIME、像素上限、上游超时、租约时长、保留期和限流阈值均为配置。未确认生产值前保持部署门禁,不由 Agent 臆造。
@@ -167,8 +167,6 @@ synchronized_at: 2026-08-22T02:09:31Z
- 当前候选版本已在本机 MySQL 8.4.8 隔离库完成空库 `up/down/up`、重复种子、Go build/vet/race、真实 portal 进程认领、Playwright 四视口和终态验证。
- 用户于 2026-08-21 明确确认 #4 验收通过;MVP-0 的全部单元任务、独立集成验收和父工单均已完成并归档。Epic #3 保持开启,后续 MVP 必须另行确认。
- 验证只使用构造用户、构造图片、mock 上游与测试 fixture;没有连接生产/共享库、调用真实 Provider 额度或发布生产。
## Provider 凭据风险决策(2026-08-22,待实施)
## Provider 凭据风险决策与 #24 候选实现(2026-08-22)
用户在了解数据库、备份、接口响应和管理员会话泄露会直接暴露上游密钥的风险后,明确接受 Provider API Key 明文存储和管理员按单条记录主动查看。该目标由 [#30](https://git.ilapage.cn/OPC/chorus/issues/30) 记录,并将在新原型确认后纳入 #24;列表不得批量返回密钥,读取响应不得缓存,真实值仍不得进入代码、日志、审计摘要、错误、工单、Wiki、原型或截图。
当前已验收代码仍使用 AES-GCM 和 `CHORUS_MASTER_KEY`,在 #24 的生产迁移完成并通过验收前,运行和部署要求保持不变。
用户已在 #30 明确接受 Provider API Key 明文落库及单条主动查看的风险。#24 候选实现已移除运行时 `CHORUS_MASTER_KEY` 依赖,迁移 000005 在存在旧加密凭据时停止,禁止猜测或丢弃;列表和普通详情不返回完整密钥,单条读取禁止缓存。管理端使用固定 go-admin/go-admin-ui 基线,Casbin 显式读取迁移管理的 `sys_casbin_rule` 并关闭 adapter AutoMigrate。该候选已通过 MySQL 8、Go、前端构建和真实账号密码/API 联调,等待用户验收。
+17 -15
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Architecture-and-Code-Map
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Architecture-and-Code-Map.-
wiki_revision: cbdeeea8fb8d037c3fe23b65e368b67bc694d767
synchronized_at: 2026-08-22T02:09:40Z
wiki_revision: c16abec724f37c0c2f72ada4a73889b084983d47
synchronized_at: 2026-08-22T03:12:13Z
<!-- gitea-wiki-mirror:end -->
# 架构与代码地图
@@ -38,7 +38,7 @@ chorus/
### #23 已落地的管理端后端
- `admin/` 是独立 Go module;从仓库根目录使用 `go -C admin build .` 和 `go -C admin test ./...`。`admin/cmd/server.go` 只注册生产 `server` 子命令,必须提供受保护的 go-admin settings 文件和 `CHORUS_MASTER_KEY`。
- `admin/` 是独立 Go module;从仓库根目录使用 `go -C admin build .` 和 `go -C admin test ./...`。`admin/cmd/server.go` 只注册生产 `server` 子命令,必须提供受保护的 go-admin settings 文件;Provider 运行时不再需要 `CHORUS_MASTER_KEY`。
- `admin/app/chorus/` 实现 Provider、ProviderModel、PromptTemplate、RoutePool、Generation 与终端用户查询 API。所有 Chorus API 经过固定 go-admin 的 JWT 与 Casbin 链;JWT 只从 `Authorization: Bearer` 读取,不接受 Query 或 Cookie token。
- 原 go-admin 的 AutoMigrate、代码生成、Swagger、WebSocket 与静态文件路由未导入生产入口。`/api/v1/chorus/` 不提供 DELETE;被引用记录使用启停而不是物理删除。
- 通用操作日志对 `/api/v1/chorus` 完全跳过请求体与结果持久化,避免把凭据写入 `sys_oper_log`;配置变更和被拒绝的探测由 `admin_audit_events` 记录脱敏摘要。
@@ -60,7 +60,7 @@ MVP-0 第一张上传图自动作为 `primary`,其余为 `reference`;不提
### #6 已落地的共享基线
- `go.mod` 固定 Go 1.26.5;`internal/config` 只从环境读取配置,生产缺少 DSN、会话密钥、Provider 主密钥或存储目录时失败关闭且不输出值。
- `go.mod` 固定 Go 1.26.5;`internal/config` 只从环境读取配置,生产缺少 DSN、会话密钥或存储目录时失败关闭且不输出值。
- `migrations/000001_mvp0_base` 建立 `users`、`providers`、`provider_models`、`prompt_templates`、`generations`、`generation_inputs`、`generation_outputs`,生产启动不调用 AutoMigrate。
- 迁移显式定义用户幂等唯一键、队列/历史索引、5 个外键和删除规则,以及状态、租约、错误、attempt 数组和输出内容检查约束。
- `internal/seed/defaults.json` 保存已确认 Prompt 和 mock ProviderModel 数据;`cmd/chorus-seed` 在事务中幂等 upsert 构造用户、mock Provider/Model 和 Prompt,不保存或输出明文凭据。
@@ -98,7 +98,7 @@ MVP-0 第一张上传图自动作为 `primary`,其余为 `reference`;不提
- `internal/core/provider.OpenAI` 实现 MVP-0 的 `chat` JSON 与 `images_edits` 多图 multipart 协议;支持文本、Base64 图片和结果 URL,`extra_body` 只接受显式白名单标量,不能覆盖 model、prompt 或 messages。
- `internal/platform/http.ProviderClient` 让 POST、重定向和结果 URL 下载共用 #8 的 SSRF 防线;携带 Authorization 的请求禁止跨 origin 重定向,避免 Bearer Key 被转发到其他主机。
- `portal/worker.GORMCatalog` 按 generation kind 强制选择恰好一个同时启用的 Provider/ProviderModel;Bearer Key 从 AES-GCM 信封解密后只在内存中交给协议 Client,不进入错误、响应或日志。
- `portal/worker.GORMCatalog` 按 generation kind 读取 Provider/ProviderModel;Bearer Key 从 `provider_credentials.api_key` 取出后只在内存中交给协议 Client,不进入错误、响应或日志。
- worker 认领后先用 lease token 绑定 ProviderModel,再读取并核对输入文件 owner/generation;文本直接形成 output,图片经格式/像素/字节校验后原子保存原图和缩略图。
- 最终成功/失败使用独立短超时上下文执行 token CAS。陈旧 token 影响 0 行时,worker 只删除本 attempt 写入的输出对象;不会删除输入或其他 worker 文件。
- 429/5xx/超时/连接错误以及 400/401/内容策略拒绝均映射为固定脱敏错误;MVP-0 记录 retryable 分类但不换 Provider、不自动重试。
@@ -106,7 +106,7 @@ MVP-0 第一张上传图自动作为 `primary`,其余为 `reference`;不提
- MySQL 8 集成测试分别完成一次 chat 和 images_edits,断言 rendered_prompt、attempt ProviderModel/latency、输出记录、原图和缩略图;全程不访问真实 Provider。
### #15 portal 组合根与运行闭环
- `portal/main.go` 现在实际组装 MySQL queue controller、AES-GCM key ring、SSRF 安全 HTTP client、Provider factory/catalog、本地存储和内嵌 worker;同步 handler 仍只写 pending,不持有 Provider 依赖。
- `portal/main.go` 现在实际组装 MySQL queue controller、SSRF 安全 HTTP client、Provider factory/catalog、本地存储和内嵌 worker;同步 handler 仍只写 pending,不持有 Provider 依赖。
- worker 的 claim 必须经过 `queue.Controller`;收到退出信号后先阻止新 claim,HTTP server 停止接收请求,并等待在途 worker 在租约上下文内完成。HTTP 或 worker 任一组件意外结束都会让进程失败关闭。
- 非生产默认 lease 60 秒、Provider HTTP 超时 45 秒、轮询 250ms、响应上限 32MiB;生产必须显式配置,且 lease 必须比 HTTP 超时多 5 秒以上。
- `CHORUS_TEST_DISABLE_WORKER=true` 只允许 `CHORUS_ENV=test`,用于浏览器稳定观察状态的测试 fixture;开发和生产环境启用会直接报错。真实 Provider/worker 成功仍通过注入受控 DNS/DialContext 的 MySQL 集成测试验证,生产 SSRF 逻辑没有旁路。
@@ -145,8 +145,8 @@ MVP-1 使用三个按顺序执行、分别可逆的迁移:`000002_admin_baseli
| 表或变更 | 关键字段与约束 | 删除规则 |
|---|---|---|
| `provider_credentials` | `provider_id`、单调 `credential_version`、AES-GCM `api_key_enc`、可检索 `key_id`、`status=active|retired`、操作者和时间;`UNIQUE(provider_id,credential_version)` | MVP-1 不物理删除;轮换只退役,回退重新激活旧版本 |
| `providers` | 增加 `active_credential_id`;旧 `api_key_enc` 在验证回填后清空,down 时从活动凭据恢复 | 被 Model/凭据引用时 RESTRICT;管理端只停用 |
| `provider_credentials` | `provider_id`、单调 `credential_version`、明文 `api_key`、`status=active|retired`、操作者和时间;`UNIQUE(provider_id,credential_version)` | 不物理删除;新版本激活后退役旧版本,可重新激活历史版本 |
| `providers` | 使用 `active_credential_id` 指向当前凭据;普通更新不得清空凭据 | 被 Model/凭据引用时 RESTRICT;管理端只停用 |
| `provider_model_capabilities` | `(provider_model_id, capability)` 唯一,capability 仅允许三种固定值 | Model 删除时 CASCADE;已有业务引用的 Model 仍不得删除 |
| `route_pools` | `slug` 唯一、`capability`、`prompt_template_id`、`max_failover`、`version`、`enabled` | 被 generation 快照引用时 RESTRICT;平时停用 |
| `active_routes` | `capability` 主键、`route_pool_id`、操作者和更新时间,保证每种能力最多一个活动池 | 路由池删除时 RESTRICT |
@@ -204,8 +204,8 @@ ClaimLease(只取得租约并记录 lease 事件)
管理 API 位于独立管理员认证/Casbin 下的 `/api/v1/chorus/`:
- providers/models/templates 使用列表、详情、创建、更新和启停;users/generations 只读列表。已被引用的记录不提供物理删除。Provider 响应只给 `has_credential`、活动 credential 版本和 `key_id`,永不返回 `api_key_enc`/明文或密文信封。
- `PUT providers/:id/credential` 只接收新明文一次,事务内加密、创建新版本、切换 active、退役旧版本并写审计;`POST providers/:id/credential/:version/activate` 可审计回退。普通 Provider 更新省略 secret 即保持不变,不能用空字符串意外清除。
- providers/models/templates 使用列表、详情、创建、更新和启停;users/generations 使用列表与必要状态操作。已被引用的记录不提供物理删除。Provider 列表和普通详情只给 `has_credential` 等元数据,不返回完整 API Key。
- `PUT providers/:id/credential` 创建新的明文 credential 版本并切换活动版本;`GET providers/:id/credential` 只允许授权管理员在单 Provider 页面主动读取,响应禁止缓存;激活历史版本继续写脱敏审计。
- route-pools 使用 `version` 乐观锁;发布活动绑定时校验能力、至少一个启用成员、正权重、PromptTemplate 兼容和 breaker 参数,不合格返回字段级错误。
- `POST provider-models/:id/connectivity-checks` 有独立 Casbin API 权限,并且仅接受 Authorization Bearer JWT,不使用 Cookie/Query token,因此没有浏览器 Cookie CSRF 通道。事务锁定最近检查并预留 running 记录;冷却内返回 429 和 `retry_after_seconds`。未显式授权时 API 写一条脱敏拒绝审计并返回 403,绝不构造出站请求。只有运行时明确设置 `CHORUS_ADMIN_ALLOW_CONNECTIVITY_PROBES=1`,并同时提供正数冷却、HTTP 超时与响应上限,服务端才按 api_type 使用固定最小探针;不接收任意 Prompt/extra_body,仍经过同一 SSRF/认证/超时链路,完成后写脱敏结果与 audit。保存配置不自动测试,CI/日常调试只用 mock;首个真实 Provider、探针和冷却值仍是上线前人工确认门禁。
@@ -221,7 +221,7 @@ ClaimLease(只取得租约并记录 lease 事件)
- 路由:权重边界、无放回、停用/open 跳过、half-open 并发、`max_failover=0/N`、无路由/全不可用/耗尽。
- retryable 红线:429、5xx、超时、连接错误分别换家;400、401、策略拒绝分别不换家;401 打开后续 circuit;旧 lease token 所有最终写入均为 0 行。
- 协议:四种 api_type 的请求/结果、能力不匹配、base64/URL 上限、Gemini inlineData、保留字段覆盖拒绝和完整 SSRF 回归;全部使用 mock。
- 密钥/管理:创建、轮换、回退、空值不清除、响应/日志/audit 无密文和明文;权限、CSRF、冷却并发只产生一个真实检查预留。
- 密钥/管理:明文保存、版本切换、空值不清除、列表/普通详情/日志/audit 不含完整值;单条读取权限与禁止缓存;探测权限、CSRF、冷却并发只产生一个真实检查预留。
- portal:role_rule 覆盖、图片一一对应/连续顺序/唯一 primary、rendered_prompt 落库、游标稳定性/篡改/跨用户以及无限滚动错误恢复。
## 代码地图
@@ -301,8 +301,10 @@ go-admin 的 AutoMigrate 只能在隔离、可丢弃数据库中用于研究固
- 生产数据库只接受 `migrations/`,不执行 AutoMigrate。
- `rendered_prompt`、`attempts`、失败 `error_code/error_message` 必须持久化。
- 输出文件先写临时文件并原子落位;图片必须有缩略图。原图、结果和缩略图访问均校验用户归属。
## 待实施架构变更:Provider 明文凭据
## #24 管理端候选实现(2026-08-22,待验收)
[#30](https://git.ilapage.cn/OPC/chorus/issues/30) 已确认目标:#24 将把 `provider_credentials` 调整为明文 API Key 存储,移除管理端 `CHORUS_MASTER_KEY` 和 KeyCipher 装配,并增加受 JWT/Casbin 保护的单 Provider 凭据读取接口。Provider 列表和普通详情不得携带完整密钥;显式读取响应设置 `Cache-Control: no-store`。若迁移发现已有加密凭据,必须停止并走受控转换,不得覆盖或丢弃。
本页前文描述的是当前已验收实现;上述变更在 #24 完成、迁移验证和用户验收前不得视为已经上线。
- `admin/app/chorus` 提供 Provider、模型、Prompt、路由池、生成记录、终端用户状态和 Provider 健康 API;所有路由位于管理员 JWT/Casbin 边界。
- `admin-ui/src/views/chorus` 提供四类 CRUD 与路由池、Provider 健康、生成详情定制页,用户页不包含点数、余额或配额。
- `migrations/000005_provider_plaintext_credentials` 切换明文凭据、菜单和权限;遇到已有加密 credential 时 fail closed,要求人工受控转换。
- Casbin adapter 绑定 `sys_casbin_rule` 并关闭 AutoMigrate;管理端启动不会创建或修改表结构。
- 列表与普通 Provider 详情不返回完整 Key;只有单条 credential 接口回显并设置 `Cache-Control: no-store`、`Pragma: no-cache`。真实值仍禁止进入日志、审计、错误、文档、工单和截图。
+7 -7
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Business-Rules-and-Glossary
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Business-Rules-and-Glossary.-
wiki_revision: 16ed0b8f397f2d921ac83528c75d20183b5e25e9
synchronized_at: 2026-08-22T02:09:43Z
wiki_revision: 85bee82b9824fa1515c511e865553f86d9a7a524
synchronized_at: 2026-08-22T03:08:23Z
<!-- gitea-wiki-mirror:end -->
# 业务规则与术语
@@ -12,7 +12,7 @@ synchronized_at: 2026-08-22T02:09:43Z
| 术语 | 含义 |
|---|---|
| **Provider** | 上游服务商账号配置,只持有名称、`base_url`、AES-GCM 密文 API Key、`key_id`、启停和审计信息。 |
| **Provider** | 上游服务商账号配置,持有名称、`base_url`、明文 API Key 凭据版本、启停和审计信息;完整 Key 只允许单条主动查看。 |
| **ProviderModel** | Provider 下可选的模型绑定,是选路最小单位;持有 `model_id`、`api_type`、能力、`extra_body`、超时、权重和启停。 |
| **api_type** | 模型绑定的调用形态:`chat`、`images`、`images_edits`、`gemini`;不属于 Provider。 |
| **RoutePool** | 按 text/image 组织的一组 ProviderModel,用于加权和故障转移。 |
@@ -64,7 +64,7 @@ synchronized_at: 2026-08-22T02:09:43Z
- `attempt_count` 继续统计租约和 Provider 事件;`provider_attempt_count` 只统计真实调用并执行上限。Begin/Finish/Succeed/Fail 全部校验 running 和当前 lease_token,陈旧 worker 只能更新 0 行。
- 429、5xx、超时、连接错误允许换家并计入熔断;400、内容策略拒绝立即失败且不计熔断;401 立即失败、不在当前任务换家,并打开该成员供后续任务避开。
- 数据库 route runtime 是跨 worker 的熔断排除依据,`gobreaker` 只作进程内快速保护;half-open 探测必须通过数据库租约限制并发。
- Provider 凭据按版本独立保存 AES-GCM 信封。轮换创建新版本并退役旧版本,回退重新激活旧密文;Provider API 只返回“是否有凭据”、版本和 key_id,不返回明文或 `api_key_enc`。
- Provider 凭据按版本独立保存明文 `api_key`;新版本激活后退役旧版本,可重新激活历史版本。列表和普通详情不返回完整值,单条查看响应禁止缓存。
- `auth_type` 只允许 none、bearer、x-goog-api-key;禁止任意认证 header。extra_body 不得覆盖 URL、认证、model、Prompt、输入、超时、输出数量和响应上限。
- 连通性检查只能由有独立权限的管理员主动触发;先原子预留检查和冷却,再使用服务端固定最小探针走生产 SSRF/认证链。冷却内返回 retry_after;保存配置、CI 和普通调试不得触发真实请求。
- image_generate 不接受输入图;image_edit 至少一张且恰好一个 primary。position 从 0 连续且唯一,role 仅 primary/reference,note 限长;Prompt 覆盖顺序为模板默认 role_rule → 非空用户 role_rule → 按 position 的图片 role/note。
@@ -105,7 +105,7 @@ synchronized_at: 2026-08-22T02:09:43Z
23. 密码使用当前批准的强哈希方案及参数并支持升级;登录按账号和来源限速,响应不泄露账号是否存在。
24. 浏览器写操作验证 CSRF;会话 Cookie 使用 `Secure`、`HttpOnly`、合适的 `SameSite`,登录后轮换 session id,登出使其失效。
25. 原图、结果和缩略图不作为无鉴权公开静态目录;每次访问校验 generation 的用户归属。JSON/HTMX 均不得跨用户查询。
26. Provider API Key 用 AES-GCM 加密;`api_key_enc` 是带 version、key_id、nonce、ciphertext 的版本化信封,主密钥不入库。明文不进日志、响应、工单、Wiki、原型或截图;轮换必须可区分旧/新密钥并可回退。
26. Provider API Key 按用户已接受的风险决策明文落库。完整值只在受 JWT/Casbin 保护的单 Provider 页面主动读取;列表、普通详情、日志、审计、错误、工单、Wiki、原型和截图不得包含真实值。
27. 管理端配置、真实连通性测试和密钥轮换写审计日志;错误和 attempts 只保留排障需要的信息。
### 范围边界
@@ -122,6 +122,6 @@ synchronized_at: 2026-08-22T02:09:43Z
- 首个 Provider/模型、真实连通性检查的最小请求与冷却时间;
- 生成物保留期、备份范围、磁盘告警和清理策略;
- 生产限流阈值及未来是否开放注册;
## 已确认待实施:Provider 凭据明文与单条回显
## 已实施待验收:Provider 凭据明文与单条回显
2026-08-22 用户明确接受风险并确认 [#30](https://git.ilapage.cn/OPC/chorus/issues/30):Provider API Key 目标状态改为明文落库,管理员可在单个 Provider 页面显式读取和再次隐藏。列表接口不得批量返回完整值,读取响应必须禁止缓存;日志、审计摘要、错误、工单、Wiki、原型和截图继续禁止记录真实密钥。现有 AES-GCM、版本信封和 `CHORUS_MASTER_KEY` 规则在 #24 完成生产迁移并验收前仍是当前行为。
2026-08-22 用户明确接受风险并确认 [#30](https://git.ilapage.cn/OPC/chorus/issues/30):Provider API Key 目标状态改为明文落库,管理员可在单个 Provider 页面显式读取和再次隐藏。列表接口不得批量返回完整值,读取响应必须禁止缓存;日志、审计摘要、错误、工单、Wiki、原型和截图继续禁止记录真实密钥。#24 候选实现已完成明文迁移、单条回显和运行时主密钥移除,并通过隔离 MySQL 8 与真实管理 API 联调;在用户验收前工单保持待验收。
+15 -6
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Local-Development-and-Verification
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Local-Development-and-Verification.-
wiki_revision: c133df3e7f58ab1d066804fbc89110bc8172541c
synchronized_at: 2026-08-22T01:15:11Z
wiki_revision: f4654349f06e495e499742354d3fb834ee08c4fd
synchronized_at: 2026-08-22T03:08:27Z
<!-- gitea-wiki-mirror:end -->
# 本地开发与验证
@@ -44,11 +44,10 @@ synchronized_at: 2026-08-22T01:15:11Z
| `CHORUS_MIGRATE_URL` | golang-migrate 专用 MySQL URL |
| `CHORUS_SEED_USER_EMAIL` / `CHORUS_SEED_USER_PASSWORD` | 构造种子用户;仅通过环境注入 |
| `CHORUS_SEED_PROVIDER_BASE_URL` | mock Provider 基础 URL;不得指向生产 |
| `CHORUS_MASTER_KEY` / 对应 key ring 配置 | Provider Key AES-GCM 解密与轮换;管理端启动也必须提供 |
| `CHORUS_ADMIN_ALLOW_CONNECTIVITY_PROBES` | 默认不设置或 `0`,管理端拒绝连通性探测;只有人工授权真实探测时才设为 `1` |
| `CHORUS_ADMIN_CONNECTIVITY_COOLDOWN_SECONDS` | 探测已授权时的正整数冷却;未授权时不需要 |
| `CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS` / `CHORUS_PROVIDER_MAX_RESPONSE_BYTES` | 探测已授权时的正整数安全 HTTP 限制 |
| `CHORUS_SESSION_KEY` | portal 会话,必须与主密钥分离 |
| `CHORUS_SESSION_KEY` | portal 会话密钥;不得与数据库、JWT 或其他凭据复用 |
| `CHORUS_STORAGE_ROOT` | 受保护生成物目录 |
| `CHORUS_ENV` | `development/test/production`,生产强制安全开关 |
| `CHORUS_SESSION_TTL_MINUTES` | 会话有效分钟数;生产必填 |
@@ -147,7 +146,7 @@ go run ./cmd/chorus-admin-bootstrap
### 启动管理端后端(#23)
先完成 migrations 和管理员 bootstrap。把 `admin/config/settings.example.yml` 复制到仓库外的受保护位置,并在其中设置独立 JWT secret 与数据库连接;示例文件不得填入真实值。当前终端安全注入 `CHORUS_MASTER_KEY` 后启动:
先完成 migrations 和管理员 bootstrap。把 `admin/config/settings.example.yml` 复制到仓库外的受保护位置,并在其中设置独立 JWT secret 与数据库连接;示例文件不得填入真实值。确认数据库已执行全部 migrations,并使用受保护的 settings 文件启动:
```powershell
go -C admin run . server --config "<受保护 settings.yml 路径>"
@@ -292,7 +291,7 @@ go -C admin test -count=1 ./...
$env:CHORUS_RUN_ADMIN_TESTS = $null
```
覆盖凭据加密、轮换和回退、普通更新不清除凭据、路由发布版本冲突、JWT/Casbin 路由边界、生产模式账号密码登录(无验证码)、禁用探测不出站,以及探测冷却只预留一次。根模块的 migration up/down/up 回归仍需在可丢弃库执行:`go test -count=1 ./migrations -run TestMVP1MigrationsUpDownUpMySQL`。
覆盖明文凭据版本切换、列表脱敏、单条读取禁止缓存、普通更新不清除凭据、路由发布版本冲突、JWT/Casbin 路由边界、生产模式账号密码登录(无验证码)、禁用探测不出站,以及探测冷却只预留一次。根模块的 migration up/down/up 回归仍需在可丢弃库执行:`go test -count=1 ./migrations -run TestMVP1MigrationsUpDownUpMySQL`。
## 完成修改前
@@ -334,3 +333,13 @@ git diff --check
| `CHORUS_PROVIDER_MAX_RESPONSE_BYTES` | 33554432 | 必须显式设置 |
`CHORUS_TEST_DISABLE_WORKER=true` 仅供 `CHORUS_ENV=test` 的 Playwright fixture 使用。测试 helper 位于 `portal/web/e2e/fixture`,会按构造用户归属写入受控状态和测试图片;不得打包部署,也不得用于开发或生产数据。mock 上游的自动测试通过依赖注入连接本地 fixture,不允许把回环地址加入生产 SSRF 白名单。
## #24 管理端本地验证(2026-08-22)
```powershell
go -C admin run . server --config admin/config/settings.yml
$env:VUE_APP_BASE_API = "http://127.0.0.1:8090"
pnpm --dir admin-ui dev
```
管理端不会创建数据库、运行 migration、seed 或 AutoMigrate。首次本地运行先人工创建空库并执行 `migrate -path migrations ... up`,再通过 `chorus-admin-bootstrap` 建立管理员。已验证纯账号密码登录、动态 Chorus 菜单和 Provider 列表 API;Casbin 使用 `sys_casbin_rule`。前端固定基线默认端口为 9527,端口占用时 Vue CLI 会选择下一可用端口。
+9 -2
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Troubleshooting
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Troubleshooting
wiki_revision: d37761a10ea18c62e0cd0680e65d9b462d79b4f5
synchronized_at: 2026-08-20T07:01:24Z
wiki_revision: 604293744eb29bd1ec4ac03d939e0305336e1d48
synchronized_at: 2026-08-22T03:08:35Z
<!-- gitea-wiki-mirror:end -->
# 故障排查
@@ -113,3 +113,10 @@ LIMIT 10;
- 需要反复真实上游调用;
- 日志疑似泄密或包含个人/生产数据;
- 同一阻塞尝试两次仍不能确认根因。
## 管理端登录后无菜单或 Chorus API 返回 403
1. 确认数据库已执行到最新 migration,`sys_casbin_rule` 中存在 `chorus_operator` 对应 `/api/v1/chorus/*` 权限。
2. 当前实现只读取 `sys_casbin_rule` 并关闭 Casbin adapter AutoMigrate;若旧进程曾生成空的 `casbin_rule`,它不是权限事实来源,不要向其中补数据。
3. 确认 bootstrap 账号绑定启用的 `chorus_operator`,重新登录取得新 JWT,再检查 `/api/v1/menurole`。
4. `settings.yml` 出现 `Unknown database` 时先创建空库并执行版本化 migration;出现 `Access denied` 时修正本地凭据,不运行 AutoMigrate 绕过。
+10 -6
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Product-Requirements-Overview
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Product-Requirements-Overview.-
wiki_revision: e47b2d87c3292dd38000b98cef93929cb9da12cb
synchronized_at: 2026-08-22T02:22:29Z
wiki_revision: 25136e6070136c8f96d489466c1e601488d09c90
synchronized_at: 2026-08-22T03:12:46Z
<!-- gitea-wiki-mirror:end -->
# 产品需求总览
@@ -33,7 +33,7 @@ synchronized_at: 2026-08-22T02:22:29Z
## 当前需求索引
工单 [#1](https://git.ilapage.cn/OPC/chorus/issues/1) 已完成长期技术基线整理和验收。项目由 [Epic #3](https://git.ilapage.cn/OPC/chorus/issues/3) 统一跟踪;[MVP-0 #4](https://git.ilapage.cn/OPC/chorus/issues/4) 已于 2026-08-21 验收完成。[MVP-1 #19](https://git.ilapage.cn/OPC/chorus/issues/19) 的设计门禁已完成:技术设计 [#16](https://git.ilapage.cn/OPC/chorus/issues/16) 已于 2026-08-21 验收通过;管理端原型 [#17](https://git.ilapage.cn/OPC/chorus/issues/17) 的 `prototypes/17/v1/index.html` 已于 2026-08-21 通过用户验收,用户端原型 [#18](https://git.ilapage.cn/OPC/chorus/issues/18) 的 `prototypes/18/v1/index.html` 已于 2026-08-21 通过用户验收。生产实现与集成验收单元工单已建立:[#20](https://git.ilapage.cn/OPC/chorus/issues/20)~[#28](https://git.ilapage.cn/OPC/chorus/issues/28)。#20、#21、#22、#23 和 #28 已于 2026-08-21~2026-08-22 验收完成;#24~#27 按声明依赖待实施。其中 #23 已交付受 JWT/Casbin 保护的管理 API、AES-GCM 凭据轮换/回退、脱敏审计和默认禁用的连通性探测门禁;portal 的 MVP-1 用户功能仍待 #25/#26 实现。
工单 [#1](https://git.ilapage.cn/OPC/chorus/issues/1) 已完成长期技术基线整理和验收。项目由 [Epic #3](https://git.ilapage.cn/OPC/chorus/issues/3) 统一跟踪;[MVP-0 #4](https://git.ilapage.cn/OPC/chorus/issues/4) 已于 2026-08-21 验收完成。[MVP-1 #19](https://git.ilapage.cn/OPC/chorus/issues/19) 的设计门禁已完成:技术设计 [#16](https://git.ilapage.cn/OPC/chorus/issues/16) 已于 2026-08-21 验收通过;管理端原型 [#17](https://git.ilapage.cn/OPC/chorus/issues/17) 的 `prototypes/17/v1/index.html` 已于 2026-08-21 通过用户验收,用户端原型 [#18](https://git.ilapage.cn/OPC/chorus/issues/18) 的 `prototypes/18/v1/index.html` 已于 2026-08-21 通过用户验收。生产实现与集成验收单元工单已建立:[#20](https://git.ilapage.cn/OPC/chorus/issues/20)~[#28](https://git.ilapage.cn/OPC/chorus/issues/28)。#20、#21、#22、#23 和 #28 已于 2026-08-21~2026-08-22 验收完成;#24 已完成候选实现并等待用户验收;#25~#27 按声明依赖待实施。其中 #23 已交付管理 API 基线;#24 按 #30 风险决策改为明文凭据与单条回显,并完成固定 go-admin-ui 页面、Casbin/菜单集成、脱敏审计和默认禁用的连通性探测门禁;portal 的 MVP-1 用户功能仍待 #25/#26 实现。
| 需求领域 | 用户与场景 | 需求状态 | MVP | 详细说明 | 实施工单 | 设计证据 |
|---|---|---|---|---|---|---|
@@ -41,7 +41,7 @@ synchronized_at: 2026-08-22T02:22:29Z
| 用户端生成页 | 种子用户登录并完成一次异步生成 | 已交付(#11~#13 已验收) | [MVP-0 #4](https://git.ilapage.cn/OPC/chorus/issues/4) | 本页“用户端布局与状态” | [#11 portal 后端](https://git.ilapage.cn/OPC/chorus/issues/11)、[#12 用户界面](https://git.ilapage.cn/OPC/chorus/issues/12) | [原型设计 #2](https://git.ilapage.cn/OPC/chorus/issues/2);`prototypes/2/v1/index.html`,2026-08-20 用户已确认 |
| 默认 prompt template | 系统以数据配置而非硬编码合成提示词 | 已交付(#7/#13 已验收) | [MVP-0 #4](https://git.ilapage.cn/OPC/chorus/issues/4) | [业务规则](Business-Rules-and-Glossary.-) | [#7](https://git.ilapage.cn/OPC/chorus/issues/7) | 2026-08-20 已确认中性模板与 `{{.UserPrompt}}` |
| 多 Provider 路由与故障转移 | 单上游故障时继续服务 | 已确认(#16 已验收,待实现) | [MVP-1 #19](https://git.ilapage.cn/OPC/chorus/issues/19) | [架构](Architecture-and-Code-Map.-)、[业务规则](Business-Rules-and-Glossary.-) | [#16 技术设计](https://git.ilapage.cn/OPC/chorus/issues/16) | #16 架构/API/数据/状态设计于 2026-08-21 已确认 |
| 管理端配置与记录 | 运营配置模型、路由池并排障 | 原型已确认(#17) | [MVP-1 #19](https://git.ilapage.cn/OPC/chorus/issues/19) | 本页“管理端页面” | [#17 管理端原型](https://git.ilapage.cn/OPC/chorus/issues/17) | `prototypes/17/v1/index.html`,v1,2026-08-21 用户已确认 |
| 管理端配置与记录 | 运营配置模型、路由池并排障 | 候选实现待验收(#24) | [MVP-1 #19](https://git.ilapage.cn/OPC/chorus/issues/19) | 本页“管理端页面” | [#17 管理端原型](https://git.ilapage.cn/OPC/chorus/issues/17) | `prototypes/17/v1/index.html`,v1,2026-08-21 用户已确认 |
| 图片角色编辑 | 用户编辑 role_rule、角色、备注与顺序 | 原型已确认(#18) | [MVP-1 #19](https://git.ilapage.cn/OPC/chorus/issues/19) | 业务规则“提示词与上传” | [#18 用户端原型](https://git.ilapage.cn/OPC/chorus/issues/18) | `prototypes/18/v1/index.html`,v1,2026-08-21 用户已确认 |
| API Key 与程序调用 | 外部程序提交与查询任务 | 已确认 | MVP-2 | 本页“程序化调用” | 待建 | API/权限设计 |
| 限流、保留期、公开注册 | 治理滥用和磁盘;决定是否开放用户获取 | 待确认(阈值/流程) | MVP-2 或后续 | 业务规则“需要补充什么” | 待建 | 安全/运维/认证设计 |
@@ -78,14 +78,14 @@ MVP-0 非目标:
- [ ] 过期 running 被新 token 原子重领,旧 worker 最终写入影响 0 行且不覆盖结果。
- [ ] 400/401/策略拒绝立即失败;429/5xx/超时/连接错误分类正确(MVP-0 不换家)。
- [ ] 私网、IPv6、redirect、代理和恶意结果 URL 被 SSRF 策略覆盖。
- [ ] API Key 密文含 key_id,日志/响应无明文;浏览器写请求有 CSRF,会话安全。
- [ ] Provider Key 按当前已确认风险决策保存;列表、日志和普通响应不含完整值,单条读取禁止缓存;浏览器写请求有 CSRF,会话安全。
- [ ] 图片输出通过鉴权访问且有缩略图;文件原子落位。
- [ ] 375/768/1024/1440 无主区域横向滚动,所有主要操作可用键盘和 44px 触控目标完成。
- [ ] 迁移在空 MySQL 8 完成 up/down/up;Go 构建、vet、测试和浏览器 E2E 通过。
#### MVP-1:可用性与运营
汇总工单为 [#19](https://git.ilapage.cn/OPC/chorus/issues/19),设计门禁已完成,生产实现单元工单已建立并等待实施:
汇总工单为 [#19](https://git.ilapage.cn/OPC/chorus/issues/19),设计门禁已完成,生产实现单元工单已建立;#24 候选实现等待用户验收:
- [x] [#16](https://git.ilapage.cn/OPC/chorus/issues/16) 路由、Provider、迁移、API、状态和测试设计(2026-08-21 验收通过);
- [x] [#17](https://git.ilapage.cn/OPC/chorus/issues/17) 固定 go-admin/go-admin-ui 的 CRUD 与三个定制页原型(`prototypes/17/v1/index.html`,2026-08-21 用户验收通过);
@@ -265,3 +265,7 @@ MVP-2 的 API Key 存哈希、身份仍属于 `users`。提交、查询、幂等
用户在获知明文存储和回显风险后明确接受风险:Provider API Key 保存后允许管理员在单个 Provider 编辑页主动查看和隐藏,目标存储形式改为明文。列表仍只展示是否已配置,不批量读取完整密钥;读取失败、无权限、加载和无凭据状态必须可恢复且可访问。
设计变更由 [#30](https://git.ilapage.cn/OPC/chorus/issues/30) 跟踪,新快照 `prototypes/30/v1/index.html` 已于 2026-08-22 通过用户验收。原 #17 v1 的“API Key 只写不可回显”部分被本变更取代,其余管理端设计继续有效。原型确认门禁已通过;#24 可按本节边界更新工单后实施生产存储、接口和管理页面。
## #24 管理端候选交付状态(2026-08-22)
固定 go-admin-ui 已导入仓库并实现 Provider、ProviderModel、PromptTemplate、Users CRUD,以及路由池、Provider 健康/主动探测和生成详情页面。Provider Key 按 #30 明文落库并仅在单条页面主动回显;用户页按 #31 完全移除点数、余额和配额。候选已通过 Go build/vet/test、前端 lint/32 个单测/生产构建、MySQL 8 migration up/down/up、管理 API 集成测试和真实账号密码/菜单/API 联调;浏览器实例不可用,未生成生产页面视觉截图。#24 保持待验收。
+12 -6
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Deployment-and-Operations
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Deployment-and-Operations.-
wiki_revision: c319c623c7e3fa0afb2a081a6b8badd57b775a9a
synchronized_at: 2026-08-21T15:02:54Z
wiki_revision: 0f7962051108f7a4bfb711cb41ffe0c0ab8b6915
synchronized_at: 2026-08-22T03:08:54Z
<!-- gitea-wiki-mirror:end -->
# 部署与运维
@@ -16,7 +16,7 @@ synchronized_at: 2026-08-21T15:02:54Z
- 生产只部署已审核制品和 `migrations/`;不从 `D:\github\goadmin` 构建,不运行 AutoMigrate。
- dev-tools 生成路由、`/gen/todb` 和写源码能力不得注册或被 nginx 代理;仅隐藏菜单不合格。
- DSN、会话密钥、Provider 主密钥/key ring、TLS 私钥只来自受控环境文件或密钥管理系统,权限最小化,不写入 Git/Wiki/日志。
- DSN、会话密钥、JWT secret、TLS 私钥只来自受控环境文件或密钥管理系统,权限最小化,不写入 Git/Wiki/日志。
- 原图、结果和缩略图保存在受保护目录,通过 portal 鉴权读取,不由 nginx 直接暴露。
- 数据迁移、回退、删除、清理和密钥轮换均是高风险操作,必须有工单、备份和人工确认。
@@ -50,7 +50,6 @@ portal → /var/lib/chorus/storage(或后续对象存储适配)
|---|---|---|
| `CHORUS_ENV=production` | 启用生产安全门禁 | 否 |
| `CHORUS_DSN` | MySQL | 是 |
| `CHORUS_MASTER_KEY` / key ring | Provider Key 加解密与轮换 | 是 |
| `CHORUS_SESSION_KEY` | portal 会话 | 是 |
| `CHORUS_STORAGE_ROOT` | 生成物目录 | 否 |
| 上游/存储超时、lease、worker 数 | 执行与优雅退出 | 否 |
@@ -61,7 +60,7 @@ portal → /var/lib/chorus/storage(或后续对象存储适配)
| `CHORUS_HISTORY_LIMIT` | 历史查询上限 | 否 |
| 保留期与磁盘告警 | 清理和容量保护 | 否 |
生产启动必须校验必需配置、密钥长度/key_id、目录权限、超时与 lease 关系;缺失或不安全时 fail closed。`CHORUS_ENV=production` 下若检测到 AutoMigrate 或 dev-tools 注册必须拒绝启动。
生产启动必须校验必需配置、会话/JWT 密钥、目录权限、超时与 lease 关系;缺失或不安全时 fail closed。`CHORUS_ENV=production` 下若检测到 AutoMigrate 或 dev-tools 注册必须拒绝启动。
## 制品构建
@@ -129,7 +128,7 @@ portal 单二进制会同时启动 HTTP server 和内嵌 worker。生产除现
| `CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS` | 正整数;安全 HTTP client 总超时和响应头超时 |
| `CHORUS_PROVIDER_MAX_RESPONSE_BYTES` | 正整数;Provider JSON/Base64/下载响应读取上限 |
`CHORUS_MASTER_KEY` 的原始字节长度必须是 AES 支持的 16、24 或 32 字节;MVP-0 写入信封使用 `key_id=primary`。生产缺失或长度无效时 portal 启动失败且不输出密钥。开发环境未配置主密钥时只生成进程内临时密钥,适用于默认 `auth_type=none` mock;要测试持久化 Bearer 凭据必须显式提供稳定的仓库外密钥。
Provider API Key 由授权管理员通过管理端写入 `provider_credentials.api_key`。数据库、备份和管理员会话泄露会直接暴露完整 Key;必须限制数据库和管理端权限,列表/普通详情/日志/audit 禁止返回完整值,单条读取响应禁止缓存。部署环境不再需要 `CHORUS_MASTER_KEY`。
`CHORUS_TEST_DISABLE_WORKER` 是测试专用开关,只在 `CHORUS_ENV=test` 接受;开发和生产设置为 true 会启动失败。部署制品和服务配置不得设置该变量,`portal/web/e2e/fixture` 也不得进入生产运行命令。
@@ -173,3 +172,10 @@ portal 单二进制会同时启动 HTTP server 和内嵌 worker。生产除现
- 高可用、多机 worker、对象存储和滚动升级尚未设计;
- 生产域名、TLS、容量、保留期、告警、RPO/RTO 和具体超时/lease 阈值待首发部署工单确认;
- 在这些门禁完成前,本页不能作为“已验证可上线”的证据。
## #24 管理端部署补充(2026-08-22)
- 先执行全部 `migrations/`,再启动 `chorus-admin server --config <受保护 settings.yml>`;启动过程不会建库、迁移、seed 或 AutoMigrate。
- Casbin 只使用 migration 管理的 `sys_casbin_rule`;发布检查应确认未生成或依赖默认 `casbin_rule`。
- `admin-ui` 静态产物通过 `pnpm --dir admin-ui build:prod` 构建,API 基址在部署时配置并由反向代理限制到管理网络。
- 首次管理员只通过仓库外环境变量运行 `chorus-admin-bootstrap`;密码不得进入服务文件、命令历史、日志或文档。