feat(gateway): 配置只读事实面 + 后台思考行「档位/穿透」(契约 v0.68)
- 新增 GET /api/v1/admin/config-facts:记忆整理 7 键 + 安静时段 2 键的**进程生效值** + 声明默认值(反射读 default 标签,不读 .env) - 面板:记忆整理卡 + 安静时段卡(Miuix 只读),文案按新语义「只服务主动搭话,不静默回复」 - 后台思考行加 urgency(**只能是下限**,存档不可分 high/critical)+ quietHoursBypassed(与既有 bypassed 是两件事) - 红线实测:往响应里塞 JWT ⇒ TestAdminConfigFacts_NeverLeaksSecretValues 打红(连断言消息都把整个响应打出来了) - 前端:configFacts 24 checks / ConfigFactsCards 29 / BackthinkUrgencyTags 19 全过 - 契约 v0.68(纯增量:B12 新端点 + B8 两字段 + §4.10 形状与语义 + §7.4-17 下限限定)
This commit is contained in:
1 parent
a057a0f61f
commit
34cef95e17
25 files changed
+1741
-14
No files matched your search
+71
-8
@@ -1,12 +1,13 @@
|
||||
# Cyrene v2 客户端 API 契约
|
||||
|
||||
> **契约版本:`v0.67`**(上一版 `v0.66`,其条目见 §6;`v0.65` 及更早的基线已归档、只读:`Docs/archive/api-client.v0.65.md`)。
|
||||
> **契约版本:`v0.68`**(上一版 `v0.67`,其条目见 §6;`v0.66` 及更早的基线已归档、只读:`Docs/archive/api-client.v0.65.md`)。
|
||||
> **读者**:Web 前端 / 移动端原生客户端(含 Android)/ IM 桥接等**跨平台客户端开发者**。
|
||||
> **唯一真源 = 当前代码**(`internal/services/gateway/*` 等)。本文档由代码逐条核对得出;与代码不符时**以代码为准**,并回改本文档。
|
||||
> **旧的超详细版已归档、只读**:`Docs/archive/api-client.v0.65.md`(412 KB,含 v0.28→v0.65 全部历史条目)与 `Docs/archive/api-reference.v0.65.md`(170 KB,后端全量字段级文档)。归档的原因是**结构性臃肿 + 历史条目并列 + 长期漂移**;本文档**不复制**那段历史,需要考古时去归档。
|
||||
> **维护铁律**:每实现/变更一个 REST 端点、WS 事件、admin API **立即改本文档**;契约变更必须版本化,不破坏既有客户端。
|
||||
> **准确性 > 详尽度**:宁少写也别写错;**写不了确证的写「未确证」**,见 [§7](#7-在飞切片与未实现未确证)。
|
||||
> ⚠️ **v0.67 是「契约先行」登记版**:本版新增的三批 —— **语音消息**(播放端点 + 消息 DTO `voice`)、**非消息事件查询面**、**账号在线状态** —— **当前尚未实现**,形状**取自设计文档**(`design/24 §10.11`、`design/26 §4.5` / `§2.6`)**而非代码核对**;已在 §7.2 逐条如实标注。**本文档其余内容仍以代码为真源**。
|
||||
> **v0.68 是纯增量版(已实现,非契约先行)**:新增 1 条管理只读端点 `GET /api/v1/admin/config-facts`(配置事实面,见 §2.2 B12),并给 `GET /api/v1/admin/backthink` 的条目加 2 个可选字段(`urgency` / `quietHoursBypassed`,见 §2.2 B8)。**无破坏性变更**,老客户端零影响。
|
||||
|
||||
---
|
||||
|
||||
@@ -119,10 +120,10 @@
|
||||
| 档位 | 中间件 | 条数 | 说明 |
|
||||
|---|:--:|:--:|---|
|
||||
| **只读监控档** | `viewerAuth`(owner/admin/viewer) | **12** | 非敏感投影:无凭据、无他人账号数据、无消息正文 |
|
||||
| **owner/admin 读档** | `writeAuth`(owner/admin) | **11** | viewer 不得看到的一切:记忆、LLM 栈、账号/key、后台思考、审计链、渠道消息日志、完整会话导出 |
|
||||
| **owner/admin 读档** | `writeAuth`(owner/admin) | **12** | viewer 不得看到的一切:记忆、LLM 栈、账号/key、后台思考、审计链、渠道消息日志、完整会话导出、配置事实面(v0.68) |
|
||||
| **owner-only 档** | `ownerAuth`(owner) | **40** | 带审计的策略写、每个获取凭据的渠道端点、其余全部变更 |
|
||||
|
||||
**管理路由总数 = 12 + 11 + 40 = 63 条。**
|
||||
**管理路由总数 = 12 + 12 + 40 = 64 条。**
|
||||
|
||||
> ⚠️ **与归档版不一致(已更正)**:`Docs/archive/api-client.v0.65.md` §2.0 写「12 + 11 + 38 = 61」(其 v0.62 条目又写「12 + 10 + 38 = 60」,自相矛盾)。**以代码为准 = 12 + 11 + 40 = 63**。
|
||||
|
||||
@@ -143,7 +144,7 @@
|
||||
| A11 | GET | `/api/v1/admin/observability/logs` | 后端日志尾部(最新优先,`?level=&limit=`;缓冲未注入时 501) |
|
||||
| A12 | GET | `/api/v1/admin/notifications` | 统一通知 feed(审计链 + 后台思考推送的只读投影) |
|
||||
|
||||
### 2.2 owner/admin 读(11 条,`writeAuth`)
|
||||
### 2.2 owner/admin 读(12 条,`writeAuth`)
|
||||
|
||||
| # | 方法 | 路径 | 要点 |
|
||||
|---|---|---|---|
|
||||
@@ -154,10 +155,11 @@
|
||||
| B5 | GET | `/api/v1/admin/llm/usage` | LLM 用量汇总(切片 U1,v0.64)。`?since=&until=&purpose=`;缺省近 24h、跨度上限 90 天(超 → `400`);`hitRate = cacheHit/(cacheHit+cacheMiss)`,分母 0 ⇒ `0`。**不含凭据与消息正文**。未接线 → `500 usage_unavailable` |
|
||||
| B6 | GET | `/api/v1/admin/llm/catalog` | 模型目录候选(`?refresh=1` 强制在线刷新)。既无快照又抓不到 → `503 catalog_unavailable` |
|
||||
| B7 | GET | `/api/v1/admin/users` | 账号/API key 清单 |
|
||||
| B8 | GET | `/api/v1/admin/backthink` | 后台思考记录 |
|
||||
| B8 | GET | `/api/v1/admin/backthink` | 后台思考记录。每条**可能**带可选 `urgency`(`"routine"`/`"warning"`/`"critical"`,`internal/urgency` 词汇;**仅在该轮档位存档可证时下发**,见 §4.10)与可选 `quietHoursBypassed:true`(该轮**穿透了安静时段**)—— **v0.68 新增** |
|
||||
| B9 | GET | `/api/v1/admin/observability/audit` | 审计链 |
|
||||
| B10 | GET | `/api/v1/admin/sessions/{id}/export` | 完整会话导出 |
|
||||
| B11 | POST | `/api/v1/admin/rag/query` | 知识库检索(带审计的读型 POST) |
|
||||
| B12 | GET | `/api/v1/admin/config-facts` | **配置事实面(v0.68 新增,只读)**:记忆整理 7 键 + 安静时段 `QUIET_START_HOUR`/`QUIET_END_HOUR` 的**进程生效值**,逐条带 `default`/`isDefault`。**不含任何密钥/令牌值**(连「是否已配置」都不回凭据类的)。形状见 §4.10 |
|
||||
|
||||
> **读取 `GET /admin/observability/{logs,metrics,trace}` 兼任可观测推送的订阅握手**(该凭据的连接全部断开即退订)。REST 响应形状不变。
|
||||
|
||||
@@ -666,6 +668,46 @@
|
||||
- 空/缺省 = 继承全局默认角色(`owner.yaml:defaultPersona`)。
|
||||
- 消息 DTO 的 `personaId` = **该消息说话时的角色**(assistant = 生成它的角色,user = 当回合角色;空 = 默认角色)。
|
||||
|
||||
### 4.10 v0.68 新增:配置事实面与后台思考行的紧急度
|
||||
|
||||
#### (a) `GET /api/v1/admin/config-facts`(只读,`owner`/`admin`)
|
||||
|
||||
响应 `{data:{facts:[…], quietHours:{…}}}`:
|
||||
|
||||
| 键 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `facts[].key` | string | env 键名,**与 `.env.example` 逐字一致**(后端有测试钉住) |
|
||||
| `facts[].value` | string | **进程生效值**(已解析:bool → `"true"`/`"false"`;整数 → 十进制;比例 → 归一化小数如 `"0.8"`)。**不是** `.env` 原文 |
|
||||
| `facts[].default` | string | 该键的**声明默认值**(`internal/config` 结构体 `default:"…"` 标签,唯一真源) |
|
||||
| `facts[].isDefault` | bool | `value == default` ⇒ 面板显示「默认」(**不代表 `.env` 里没写过这个键**:网关按红线**不读 `.env`**,无从不区分「默认层」与「结构体默认」) |
|
||||
| `quietHours.startHour` / `endHour` | int | 安静时段的生效钟点(本地时区小时)。⚠️ 报的是**运行时真正生效**的那一个:两端都写 0 时按「未填」回落到声明默认(见下 `fallbackToDefault`) |
|
||||
| `quietHours.disabled` | bool | 窗口是否**关闭**(两个相等的**非 0** 钟点,如 `23/23`) |
|
||||
| `quietHours.label` | string | 面板可直接显示的文案:`"23:00-07:00"` 或 `"已关闭"` |
|
||||
| `quietHours.fallbackToDefault` | bool,**可选**(`true` 才出现) | 两端都被写成 `0` ⇒ 视为**未填**,运行时回落到默认窗口(`23:00-07:00`,此时 `startHour`/`endHour` 已是回落后的值)。⚠️ **`0/0` 不是「关闭」**:backthink 与记忆整理都会做这次回落,事实面按同一口径报,否则会把「未填」谎报成「已关闭」 |
|
||||
|
||||
- **固定 9 条事实**(记忆整理 7 键 + 安静时段 2 键):`MEMORY_TIDY_ENABLED`、`MEMORY_TIDY_INTERVAL_SEC`、`MEMORY_TIDY_QUIET_START_HOUR`、`MEMORY_TIDY_QUIET_END_HOUR`、`MEMORY_TIDY_RECALL_THRESHOLD`、`MEMORY_TIDY_SCAN_LIMIT`、`MEMORY_TIDY_CANDIDATE_LIMIT`、`QUIET_START_HOUR`、`QUIET_END_HOUR`。
|
||||
- 🔴 **不含任何凭据**:响应里不出现 JWT 密钥 / LLM API key / 渠道 token / 数据库 DSN 的值(后端有哨兵测试钉死),**也不回凭据类键的「是否已配置」** —— 本端点只覆盖上面 9 个非敏感调参键。
|
||||
- **「默认 / 已改」怎么读**:`isDefault:true` ⇒ 面板显示「默认」(生效值等于该键的声明默认值);`isDefault:false` ⇒ 显示「已改」。⚠️ 它**不是**「`.env` 里没写过这个键」的断言 —— 网关按红线**不读 `.env`**,无从不区分「`.env` 给的值恰好等于默认」与「完全没配」。**哪一项没配**因此不会被伪造成一个值:面板拿 `value` + `isDefault` 如实展示即可。
|
||||
- 9 条事实的 `default` **必须都能取到**(后端有测试逐条钉住非空):取不到即「配置结构体 ↔ 事实表」漂移,属缺陷,**不是**「未配置」。
|
||||
- **安静时段的语义(重要,别照旧实现写文案)**:安静时段**只服务「定时主动思考 → 主动搭话」**(`design/12 §6` 策略五),**不静默回复** —— 白天晚上的聊天回复都不受它约束;群聊也没有夜间静默(主人裁决 10)。因此面板文案**不得**写成「夜间全静默」。
|
||||
|
||||
#### (b) `GET /api/v1/admin/backthink` 条目的 v0.68 新字段
|
||||
|
||||
| 键 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `urgency` | string,**可选** | `"routine"` / `"warning"` / `"critical"`(`internal/urgency` 的档位词汇)。**语义 = 该轮档位的「存档可证下限」**:`warning` 读作「至少 high 档(可能是 critical)」 |
|
||||
| `quietHoursBypassed` | bool,**可选**(`true` 才出现) | 该轮**穿透了安静时段**:轮次时间落在安静时段窗口内**且**该轮判定为「要推送」 |
|
||||
|
||||
- **两个键都可能缺席,缺席 ≠ 常规档位**。缺席 = **存档里没有这个事实**(见 §4.10(c)),面板应显示「未知」而**不是**「例常」。
|
||||
- `urgency` 只在该轮时间**落在安静时段窗口内**时下发:安静时段内被放行 ⇒ `warning`(由 `backthink.Decide` 的级联可证:安静时段只放行 ≥ `high` 的轮次)且 `quietHoursBypassed:true`;安静时段内被扣下 ⇒ `routine`。窗口之外的轮次判定不看档位、存档也不记录档位,故**不下发**。窗口口径与 B12 的 `quietHours` **同一份**(含「`0/0` 视为未填、回落到默认」这条),因此 0/0 的部署也不会漏报穿透。
|
||||
- `quietHoursBypassed` 与既有的 `bypassed`(v0.66 前已有,含义是**事件驱动绕过限频闸**)是**两件事**,别混用:前者说「安静时段」,后者说「限频门」。
|
||||
- ⚠️ **实时帧不带这两个键**:`/ws/chat` 下行的 `backthink` 帧其 data 是 bus 上发布的原始 `backthink.Record`(Go 字段名 `ID`/`Trigger`/`ShouldPush`/`PushResult`…,**没有** `urgency`/`quietHoursBypassed`)⇒ 客户端拿到实时行时这两个事实**不可得**(管理面板因此先显示「档位未知」,重开页面由 `GET /admin/backthink` 补真值)。**本版刻意不改 WS 帧**(改它就是动既有帧形状,属另一件事);要精确档位需 backthink 把 `Import` 落进记录。
|
||||
|
||||
#### (c) 为什么档位只能是「下限」而不是精确值(诚实边界)
|
||||
|
||||
- 持久化的思考记录(`backthink.Record`)**不带** `Import`/重要度字段,且推送成功时 `PushResult.Detail` 会被投递说明覆盖 ⇒ 精确档位(`high` 还是 `critical`)**在存档里已丢失**。
|
||||
- 本契约因此**只下发可证的部分**,不猜:要显示精确档位,需 `internal/services/backthink` 把该轮 `Import` 落进记录(**本版不做**,见 §7.4-17)。
|
||||
|
||||
---
|
||||
|
||||
## 5. env 与模型配置(与契约相关)
|
||||
@@ -701,7 +743,14 @@
|
||||
| `LLM_PROVIDER` / `LLM_BASE_URL` / `LLM_API_KEY` / `LLM_MODEL` / `LLM_FALLBACK_MODEL` / `LLM_REASONING_KIND` 等 | 见模板 | LLM provider 组的 `.env` 合成组兜底(有有效 `models.json` 时被其覆盖) |
|
||||
| `DOCUMENT_GC_INTERVAL` / `DOCUMENT_RETENTION` | `1h` / `168h` | 文档 GC(`0`/负 = 关闭 GC) |
|
||||
| `LLM_USAGE_RETENTION` | `720h` | `llm_usage` 保留期(`0`/负 = 关闭清理) |
|
||||
| `THINK_*` / `QUIET_*` | 见模板 | 后台思考与主动推送策略 |
|
||||
| `THINK_*` / `QUIET_*` | 见模板 | 后台思考与主动推送策略。`QUIET_START_HOUR`(`23`)/ `QUIET_END_HOUR`(`7`)是**本地钟点**的安静时段窗口(跨午夜可用;两个相等的非 0 钟点 = 关闭;两端都为 0 = 未填 ⇒ 回落默认 `23/7`):**只服务「定时主动思考 → 主动搭话」**,**不静默回复**(v0.68 起可经 B12 读到生效值) |
|
||||
| `MEMORY_TIDY_ENABLED` | `true` | 记忆整理总开关(`design/23 §4`)。关闭 ⇒ 完全不跑(日志写明);整理**只产建议、绝不自动改记忆** |
|
||||
| `MEMORY_TIDY_INTERVAL_SEC` | `86400` | 整理周期(秒),是**下限**:实际在低峰窗口起点跑 ⇒ 真实间隔取「窗口每天一次」与本值的较长者 |
|
||||
| `MEMORY_TIDY_QUIET_START_HOUR` / `MEMORY_TIDY_QUIET_END_HOUR` | `23` / `7` | 记忆整理的**低峰窗口**(本地钟点,默认与 backthink 同值);两端相等且非 0 = 关闭窗口、改为纯周期;两端都为 0 = **未填**,运行时回落到默认 `23/7` |
|
||||
| `MEMORY_TIDY_RECALL_THRESHOLD` | `0.80` | 候选**召回门**(余弦),**不是**合并判据(可否合并由包含关系 + 规则否决决定) |
|
||||
| `MEMORY_TIDY_SCAN_LIMIT` / `MEMORY_TIDY_CANDIDATE_LIMIT` | `5000` / `20` | 每分区每轮的扫描上限 / 候选上限 |
|
||||
|
||||
> **上面 9 个键(记忆整理 7 + 安静时段 2)自 v0.68 起可经 `GET /api/v1/admin/config-facts` 读到**「进程生效值 + 声明默认值」;在此之前面板没有任何 env 读取面。
|
||||
| `SUBAGENT_TIMEOUT_SEC` | `600` | 单个子代理整次运行时限 |
|
||||
| `MCP_SERVERS_JSON` | 空 | MCP server 注册表(JSON 数组)。⚠️ 扁平 `MCP_NAME`/`MCP_COMMAND`/… 为**未接线残留键,配了不生效** |
|
||||
| `CHANNEL_*` / `CHANNELS_JSON` | 见模板 | 渠道接入(`.env` 为兜底,面板 `channels.json` 优先) |
|
||||
@@ -722,7 +771,19 @@
|
||||
> 只保留最近若干版(**v0.60 起**);更早的全部历史见归档 `Docs/archive/api-client.v0.65.md` §5(v0.28 → v0.59)。
|
||||
> 每版一句话 + 三档标注。**三档定义见 [§0.4](#04-三档标注惯例)。**
|
||||
|
||||
### v0.67(当前契约版本)
|
||||
### v0.68(当前契约版本)
|
||||
|
||||
**管理面「配置事实」只读端点 + 后台思考行的紧急度/穿透字段**(依据主人指正「你多久没把新功能同步管理面板更新了?」与裁决 14;**本版全部已实现、非契约先行**)。
|
||||
|
||||
- **纯增量**:
|
||||
① **新 admin 端点 `GET /api/v1/admin/config-facts`**(`owner`/`admin`,只读、不审计):返回 9 条配置事实(记忆整理 7 键 + `QUIET_START_HOUR`/`QUIET_END_HOUR`)的**进程生效值**(`value`)+ **声明默认值**(`default`)+ `isDefault`,外加 `quietHours{startHour,endHour,disabled,label}` 的窗口解释 —— **§2.2 B12 / §4.10(a)**。**不含任何凭据值**(连凭据类键的「是否已配置」都不回)。权限矩阵随之更新:`writeAuth` 11 → **12**、总数 63 → **64**。
|
||||
② `GET /api/v1/admin/backthink` 的条目增 2 个**可选**字段:`urgency`(`routine`/`warning`/`critical`)与 `quietHoursBypassed`(`true` 才出现)—— **§2.2 B8 / §4.10(b)**。语义:`urgency` 是**存档可证的下限**(`warning` = 至少 `high`),`quietHoursBypassed` = 「这轮在安静时段里仍被放行发出去」。
|
||||
③ §5 登记 9 个键的默认值与语义(此前只以 `THINK_*`/`QUIET_*` 概括)。
|
||||
- **行为变更**:无(既有两个端点的既有键一字未改;新字段都是可选键,老客户端不读即零影响)。
|
||||
- **破坏性**:无。
|
||||
- ⚠️ **诚实边界**:后台思考的**精确**档位(`high` 还是 `critical`)在存档里**已丢失**(`backthink.Record` 不带重要度,推送成功的行其 `detail` 被投递说明覆盖)⇒ 本版只下发**可证下限**,窗口外的轮次干脆不下发(面板显示「未知」),**绝不猜**。见 §4.10(c) / §7.4-17。
|
||||
|
||||
### v0.67(上一版)
|
||||
|
||||
**三批已在设计文档定稿的新增面一次性登记**:语音消息(播放端点 + 消息 DTO `voice` + 音频副本口径)、非消息事件查询面、账号在线状态(依据 `design/24 §10.11`、`design/26 §4.5` / `§2.6`)。登记时三批**都还没有实现**;其中**账号在线状态已落地**(见下)。
|
||||
|
||||
@@ -884,6 +945,8 @@
|
||||
15. **(v0.67 登记)`GET /admin/system/storage` 的 `documents` 段是否拆出语音副本**:设计**本期不拆**(不新增 `kind` 列 / 不按 ext 聚合)⇒ 该段**只增口径、不变形状**,客户端**不要**指望能分开统计。
|
||||
16. **(v0.67 登记 · 前向提示)`voice.url` 指向的可能不是原件,而是转码后的副本**:`design/24 §10.14` 已把「源格式不可播 ⇒ 转码成可播副本(唯一必需的路 = `amr → wav`)」立为口径,转码能力规格见 `design/25-转码能力.md`(**该能力尚未接线**)⇒ 客户端**不要假定 `url` 一定是某一种音频格式**,一律**按响应的 `Content-Type` / `mime` 如实处理**,播不了就照实提示(**绝不给一个播不了的 `url` 假装能播**)。⚠️ 本条**不在 `design/24 §10.11` 的差异清单内**(§10.11 与 §10.13 均无「零转码」绑定表述),登记它是因为「`url` 的字节格式」会直接影响播放端;**最终口径以 `design/25` 为准**。
|
||||
|
||||
17. **(v0.68 · 本版限定)`urgency` 是「档位下限」,不是精确档位**:`warning` 读作「至少 `high`(也可能是 `critical`)」;**窗口之外的轮次不下发该键**(`backthink.Record` 不带重要度、推送成功的行 `detail` 被投递说明覆盖 ⇒ 无损精确档位已不可能)。客户端**不要**把「键缺席」显示成「例常 / routine」,也不要据此推断安全域。要精确档位需 backthink 侧把每轮 `Import` 落进记录(**本版未做**)。
|
||||
|
||||
---
|
||||
|
||||
*本文件由「以代码为真源」维护(当前 `v0.67`;`v0.66` 见本文件 §6,`v0.65` 及更早见 `Docs/archive/`)。范围:只改 `Docs/**`,未改动任何代码。*
|
||||
*本文件由「以代码为真源」维护(当前 `v0.68`;`v0.67` / `v0.66` 见本文件 §6,`v0.65` 及更早见 `Docs/archive/`)。范围:只改 `Docs/**`,未改动任何代码。*
|
||||
Reference in new issue
Block a user