feat(admin): 面板「存储」卡片接真数据(含 PG 增强状态)+ 契约 v0.60
- 现状:overviewMetrics.ts:188 的「存储」卡是写死的 value:"SQLite",后端也没有对应读端点(/admin/system/* 只有 revoke-keys/rotate-jwt/backup/restore 四个动作端点)。
- 新增只读端点 GET /api/v1/admin/system/storage(ownerAuth):main{kind,path,fileBytes,walBytes,shmBytes,pageSize,pageCount,freelistPages,freeBytes} + documents{count,bytes} + vector{kind,enabled,ready};只读(SELECT/PRAGMA + os.Stat)、复用活连接不开新池、**绝不含凭据**(只回 kind,不回 host/DSN)、失败一律 500 storage_unavailable(不回全 0 假快照;表缺失也 500 而不是静默 0)。
- vector 真源 = 组合根 app.vectorState()(Configured = DB_PG_URL 非空;Live = 向量索引类型断言成功),零新探测 ⇒ 面板现在能区分「未启用(未配置 DB_PG_URL)/ 启用(pgvector·ready)/ 已配置但未就绪(连接失败,检索降级)」,正好解答主人对「面板只写 SQLite」的困惑。
- 前端:卡片显示真实体积 + WAL/页数/空闲页 + documents 件数与占用 + PG 那一行;取数走与已接线卡片同一条加载/错误态(失败显示 '—' + 端点报错,不静默显示 0)。
- 契约 v0.60(项目红线):api-client(版本段 + 端点表 + 存储状态契约小节 + changelog)与 api-reference(版本号 + 端点表 + 详情小节)同步;字段名三方(代码/Docs/前端)逐字对齐。
- 主会话独立验收:隔离 worktree(ST1 的 14 文件,排除 B1 在途的 chat_documents.go/documents_pdf_test.go)build/vet 绿、全树 59 包 0 FAIL、存储端点 7 用例 PASS、双平台交叉编译绿。
- ⚠️ 该切片自首一起事故:为通过 cmd/cyrene 类型检查跑了 go build ./cmd/cyrene/(单 main 包)⇒ 覆盖了仓库根 cyrene.exe(49,675,264 B / 19:32:19,脏树未 strip);上一个权威产物仍在 cyrene.exe~(39,507,968 B / 19:25:13)。运行中的 PID 477964 未受影响;主会话将在整树干净后重跑 ctrl.bat build 恢复权威产物。
This commit is contained in:
1 parent
137b3e9eaf
commit
c4e09afd38
14 files changed
+751
-13
No files matched your search
+44
-2
@@ -1,6 +1,6 @@
|
||||
# Cyrene v2 后端 API 参考(api-reference.md)
|
||||
|
||||
> **权威归属(先读这条)**:**契约以 `Docs/api-client.md` 为准**——那是版本化的跨平台客户端契约,每个端点的形状 / 语义 / 版本变更都记在那里(当前 `v0.58`)。**本文件是内部与运维参考**:端点全集索引 + 示例 + 部署细节。**凡是与 api-client 重复的契约性描述,一律以 api-client 为准(本文件不再维护第二份契约);发现两者不一致时,改本文件而不是改 api-client。**
|
||||
> **权威归属(先读这条)**:**契约以 `Docs/api-client.md` 为准**——那是版本化的跨平台客户端契约,每个端点的形状 / 语义 / 版本变更都记在那里(当前 `v0.60`)。**本文件是内部与运维参考**:端点全集索引 + 示例 + 部署细节。**凡是与 api-client 重复的契约性描述,一律以 api-client 为准(本文件不再维护第二份契约);发现两者不一致时,改本文件而不是改 api-client。**
|
||||
> **本文件的正确用法**:查「有哪些端点、大致形状、错误码、部署注意事项」可以;**写客户端代码时请以 `Docs/api-client.md` 为准**,本文件的字段表 / 示例可能滞后。
|
||||
> **历史包袱(如实)**:本文件长期自称「全部后端 API 的唯一详细参考 / 超详细主文档 / 每变更必同步」,但实际**系统性滞后于实现与 api-client**(2026-10-03 的 16 路 design-vs-code 审计结论)。本次已修正与实现**直接矛盾**的陈述、并把若干复述型段落改为引用 api-client;**§1 的 REST 端点表已于 2026-10-04 按 `internal/services/gateway/routes.go` 逐条核对并补登记**(补上此前缺失的 `POST /auth/refresh`、`POST /auth/logout`、`POST /upload`、`GET /files/{name}`、三支 `/audio/*`、`GET /admin/llm/catalog`、`GET /admin/observability/logs`、`GET /admin/notifications`、`GET /admin/plugins/installed`、`GET|PUT /admin/owner-profile`、`POST /admin/automations/reminders`)。**仍未补全的是 WS 事件帧清单**:§6.4 自述「并非全表」,`DevDocs/audit/00-总览.md` §4④ 列出的 `session_read` / `message_edited` / `message_deleted` 等仍未逐行登记。
|
||||
> **其余仍成立**:所有字段名、取值、鉴权规则、状态码最终**以代码为准**(`internal/services/gateway/*.go`);契约变更版本化(REST 走 `/api/v1`,破坏性变更升 v2),不得破坏既有客户端(版本化承诺本身记在 api-client §5)。
|
||||
@@ -154,10 +154,11 @@
|
||||
| 59 | POST | `/api/v1/admin/system/rotate-jwt` | **owner** | 200 | 轮换 JWT 密钥 | M10/M11 |
|
||||
| 60 | POST | `/api/v1/admin/system/backup` | **owner** | 200 | 备份数据库 | M10/M11 |
|
||||
| 61 | POST | `/api/v1/admin/system/restore` | **owner** | 200 | 恢复数据库 | M10/M11 |
|
||||
| 69 | GET | `/api/v1/admin/system/storage` | **owner** | 200/500 | **存储状态(只读,v0.60)**:SQLite 文件/WAL/页级统计 + `documents` 表件数与占用 + 向量增强真假(`kind`/`enabled`/`ready`);**不含凭据**(DSN 一个字都不回);路径/活连接未接入或读不到表 → `500 storage_unavailable`(见 §5.35) | M10/M11 |
|
||||
|
||||
**另**:外部 webhook 回调(如 `webhook` 渠道的 HMAC 回调)由 composition root 通过 `Deps.Webhooks` 动态挂载,**不走网关鉴权**(handler 自校验签名/授权);路径由调用方决定,不在上表枚举。
|
||||
|
||||
**补登记说明(2026-10-04)**:上表中 `#62`–`#68` 是本次按 `routes.go` 逐条核对后新增的行(`plugins/installed` / `observability/logs` / `notifications` / `llm/catalog` / `owner-profile` GET+PUT / `automations/reminders`),插在各自的鉴权分档里、**既有编号一律未动**。§1.1 的 `#3`/`#4`(`auth/refresh`、`auth/logout`)与 §1.2 的 `#14`–`#18`(`upload`、`files/{name}`、三支 `audio/*`)同理。**v0.59 再补两行**:§1.2 的 `#19`/`#20`(`POST /api/v1/documents`、`GET /api/v1/documents/{name}`,均按 `routes.go` 的 `writeAuth` 注册核对)——同样取本表下一个可用号、不动既有编号。**仍未登记的是 WS 事件帧**(见 §6.4 与本文档开头的历史包袱说明)。
|
||||
**补登记说明(2026-10-04)**:上表中 `#62`–`#68` 是本次按 `routes.go` 逐条核对后新增的行(`plugins/installed` / `observability/logs` / `notifications` / `llm/catalog` / `owner-profile` GET+PUT / `automations/reminders`),插在各自的鉴权分档里、**既有编号一律未动**。§1.1 的 `#3`/`#4`(`auth/refresh`、`auth/logout`)与 §1.2 的 `#14`–`#18`(`upload`、`files/{name}`、三支 `audio/*`)同理。**v0.59 再补两行**:§1.2 的 `#19`/`#20`(`POST /api/v1/documents`、`GET /api/v1/documents/{name}`,均按 `routes.go` 的 `writeAuth` 注册核对)——同样取本表下一个可用号、不动既有编号。**v0.60 再补一行**:`#69` GET `/api/v1/admin/system/storage`(owner-only 只读,按 `routes.go` 的 `ownerAuth` 注册核对;**无审计动作**——它是查询不是动作),同样取下一个可用号、既有编号未动。**仍未登记的是 WS 事件帧**(见 §6.4 与本文档开头的历史包袱说明)。
|
||||
|
||||
**WS**:`GET /ws/chat`(握手,JWT,见 §6)。
|
||||
|
||||
@@ -1146,6 +1147,47 @@ curl -s -X POST http://<host>:8080/api/v1/admin/system/restore \
|
||||
-d '{"path":"/abs/path/data/backup-20260908T000000.000000000Z.db"}'
|
||||
```
|
||||
|
||||
### 5.35 GET /api/v1/admin/system/storage(v0.60,只读)
|
||||
|
||||
- **鉴权**:**owner**(`ownerAuth`,与四条 `POST /system/*` 动作端点同门)。无凭据 / 非 owner → `401`。
|
||||
- **Body / query**:无。
|
||||
- **性质**:**只读查询**,**没有审计动作**(审计只覆盖动作)。实现只有 `SELECT`/`PRAGMA` 与 `os.Stat`,且复用组合根注入的**活连接**(`Deps.Database`)——**不新开连接池、不写、不迁移、不备份**。调用前后 `documents` 行数与数据目录内文件集合均不变。
|
||||
- **响应**:`200`
|
||||
|
||||
```json
|
||||
{"data":{
|
||||
"main":{"kind":"sqlite","path":"/srv/cyrene/data/cyrene.db","fileBytes":12894208,
|
||||
"walBytes":107872,"shmBytes":32768,"pageSize":4096,"pageCount":3148,
|
||||
"freelistPages":12,"freeBytes":49152},
|
||||
"documents":{"count":37,"bytes":2143567},
|
||||
"vector":{"kind":"none","enabled":false,"ready":false}
|
||||
}}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `main.kind` | string | 恒 `"sqlite"`(当前唯一主库引擎) |
|
||||
| `main.path` | string | 组合根实际打开的库文件路径(与备份/恢复端点同源) |
|
||||
| `main.fileBytes` | int | 主库文件字节数(`os.Stat`) |
|
||||
| `main.walBytes` / `main.shmBytes` | int | `<db>-wal` / `<db>-shm` 边车字节数;**合法地不存在时如实为 `0`**(已 checkpoint / 未开 WAL) |
|
||||
| `main.pageSize` / `main.pageCount` / `main.freelistPages` | int | `PRAGMA page_size` / `page_count` / `freelist_count`(一条 SELECT 取完) |
|
||||
| `main.freeBytes` | int | `pageSize × freelistPages`(已分配但空闲、VACUUM 可回收的量级) |
|
||||
| `documents.count` | int | `documents` 表行数 |
|
||||
| `documents.bytes` | int | `SUM(documents.size)`;空表为 `0`(`COALESCE`),**不是** `null` |
|
||||
| `vector.kind` | string | `"pgvector"` = 已配置 `DB_PG_URL`;`"none"` = 未配置 |
|
||||
| `vector.enabled` | bool | 增强开关在不在(等价于 `kind != "none"`) |
|
||||
| `vector.ready` | bool | **活着的**向量索引确实是 PostgreSQL+pgvector 后端 |
|
||||
|
||||
- **`vector` 的读法(重要)**:`kind` 说「配没配 `DB_PG_URL`」,`ready` 说「现在真的在跑什么」。配了 PG 但连接/schema 准备失败 ⇒ `enabled:true` + `ready:false`(检索已降级),客户端应显示「已配置但未就绪」,**不要把 `enabled` 当成生效**。`kind:"none"` 时前端显示「未启用(未配置 `DB_PG_URL`)」。**真源** = 组合根 `cmd/cyrene/wire_frontend.go` 的 `app.vectorState()`(`DB_PG_URL` 非空 + 活索引的具体类型),网关不读环境变量、不新开 PG 连接、不自行探测。
|
||||
- **不含凭据(红线)**:DSN / 用户名 / 密码 / `postgres://` 全文**一个字都不回**,`vector` 段只有 `kind`/`enabled`/`ready` 三个键(有用例断言响应体里搜不到这些串)。
|
||||
- **错误**:`401`(无凭据 / 非 owner);`500 {"error":{"code":"storage_unavailable","message":…}}` —— 数据库**路径**未配置、活**连接**未注入、或页级统计 / `documents` 表读不到(例如库没跑迁移)。**诚实边界**:此时**不返回**任何全 0 的假快照。
|
||||
- **前端落点**:`frontend/web/src/lib/api/adminSystem.ts` 的 `adminSystemStorage()` → 面板首页「存储」卡(`admin/modules/dashboard/overviewMetrics.ts`):`value` = `SQLite 12.3 MiB`、`hint` = WAL / 页数 / 空闲页、详情两行 = `documents N 件 · …` 与 `PG 增强 …`。取数走与其它已接线卡片同一条加载/错误态(失败显示端点错误,不回退成 `0`)。curl:
|
||||
|
||||
```bash
|
||||
curl -s http://<host>:8080/api/v1/admin/system/storage \
|
||||
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. WebSocket 实时协议(`GET /ws/chat`)
|
||||
|
||||
Reference in new issue
Block a user