docs(契约): v0.89 登记助手消息的行内标签白名单
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

依据:主人 2026-10-07 要求前端支持这类标签的基本渲染(Web 端实现已推库
2dae469,真机产物已一致)。

- 白名单:b/strong、i/em、code、u、s(成对)+ br(void),大小写不敏感
- 三条硬口径:白名单外按纯文本原样显示(字符一个不丢)/ 属性一律不解析 /
  结构不平衡(未闭合、交错)整段降级为原文
- 安全红线:助手文本是不可信内容,任何端都不许整体当 HTML 注入;
  正确姿势是解析成结构化节点、交给各自的安全渲染原语
- 范围克制:不做块级(段落仍走 §4.16 的 p 机制)、不做 a、不做 Markdown
- 服务端零变更:无新端点 / 帧 / 字段,计数仍 14 + 25 + 40 = 79
This commit is contained in:
AskaEth committed 2026-10-07 17:15:32 +08:00
1 parent 2dae4693be
commit b989f0bb0c
1 file changed
+20 -2
+20 -2
View File
@@ -1,6 +1,6 @@
# Cyrene v2 客户端 API 契约
> **契约版本:`v0.88`**(上一版 `v0.87`,其条目见 §6;`v0.86` / `v0.85` / `v0.84` / `v0.83` / `v0.82` / `v0.81` / `v0.80` / `v0.79` / `v0.78` / `v0.77` / `v0.76` / `v0.75` / `v0.74` / `v0.73` / `v0.72` / `v0.71` 及更早见本文件 §6,`v0.67` 及更早的基线已归档、只读:`Docs/archive/api-client.v0.65.md`)。
> **契约版本:`v0.89`**(上一版 `v0.88`,其条目见 §6;`v0.87` / `v0.86` / `v0.85` / `v0.84` / `v0.83` / `v0.82` / `v0.81` / `v0.80` / `v0.79` / `v0.78` / `v0.77` / `v0.76` / `v0.75` / `v0.74` / `v0.73` / `v0.72` / `v0.71` 及更早见本文件 §6,`v0.67` 及更早的基线已归档、只读:`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,后端全量字段级文档)。归档的原因是**结构性臃肿 + 历史条目并列 + 长期漂移**;本文档**不复制**那段历史,需要考古时去归档。
@@ -1487,7 +1487,25 @@ curl -s -X POST -H "X-API-Key: $KEY" \
> 只保留最近若干版(**v0.60 起**);更早的全部历史见归档 `Docs/archive/api-client.v0.65.md` §5(v0.28 → v0.59)。
> 每版一句话 + 三档标注。**三档定义见 [§0.4](#04-三档标注惯例)。**
### v0.88(当前契约版本)
### v0.89(当前契约版本)
**「助手消息的行内标签白名单」版 —— 只影响渲染口径,**服务端 API 形状一字未改****(依据:主人 2026-10-07「她在聊天消息中插入了 html/md 标签,能否增加网页前端对此类标签的基本渲染支持?」;Web 端实现推库 `2dae469`。)
- 🔴 **背景(实测)**:她输出的**不是 Markdown**(最近 12 条 assistant 消息里 `**` 记号 0 个、列表行 0 个),而是**有限的一小组 HTML 标签**:`<p>` 96 次、`<action>` 34 次、**`<b>` 6 次**。`<p>` / `<action>` 各端早已处理,**`<b>` 此前没有任何一端处理 ⇒ 用户直接看到裸标签**。
- **新增渲染口径(跨端一致)**:助手消息正文里,以下**行内标签**应被渲染成对应样式,而**不是**按字面显示:
`b` / `strong`(粗)· `i` / `em`(斜)· `code`(等宽)· `u`(下划线)· `s`(删除线)· `br`(换行,`<br>`/`<br/>`/`</br>` 按 HTML5 口径等价)。
**标签名大小写不敏感**(`<B>` = `<b>`)。
- **🔴 三条硬口径(各端必须照此实现,否则跨端显示不一致)**:
1. **白名单外一律按纯文本原样显示** —— `<script>` 显示成字面量 `&lt;script&gt;`,**字符一个不丢**;
2. **属性一律不解析** —— `<b class=x onclick=y>` 只取标签名,**属性不得变成任何 DOM/视图属性**;
3. **结构不平衡**(未闭合 / 落单闭标签 / 交错嵌套)⇒ **整段降级为纯文本原文**(粗粒度是有意为之:换来「字符一个不丢」可被逐字断言)。
- 🔴 **安全红线**:助手文本是**不可信内容**(模型生成 + 用户可诱导)⇒ **任何端都不许把整段内容当 HTML 注入**(Web 端禁 `dangerouslySetInnerHTML`,原生端禁等价手段)。正确姿势是**解析成结构化节点、交给各自的安全渲染原语**(Web:React 子节点自动转义)。Web 端已由「**全程无 `dangerouslySetInnerHTML`** + 节点类型里**没有属性位**」两条结构性保证锁住,并以「白名单外的 `script` 必须逐字原样」为测试断言。
- **范围克制**:**不做块级**(段落仍由 §4.16 的 `<p>` 机制表达)· **不做 `<a>`**(链接有独立安全面,且她目前不用)· **不做 Markdown**(她不用)。
- **不改既有**:`<action>` / `<narration>` 的既有行为逐字不变(Web 灰斜体、IM 出口剥除);**注记内部不参与行内渲染**(注记里写 `<b>` 仍是字面量)。
- **无回归证据**:Web 端改动前后拿 11 个消息样例过**真实气泡切分链路**逐字节对照 —— 7 个逐字节相同,4 个差异恰是含白名单标签的样例。
- **兼容性**:**服务端零变更**(无新端点/帧/字段,§2.0 计数不变)。对本契约的读者而言,本版是**新增的渲染约定**:老客户端不跟进 ⇒ 行为与 v0.88 相同(**只会继续显示裸标签**,不会崩)。
### v0.88
**「单端口红线的适用范围澄清」版 —— 不改任何端点 / 帧 / 字段,只澄清一个边界**(依据:主人 2026-10-07 口径补充「关于不允许开新端口的红线,我加一个补充。那是对 cyrene 框架自身的约束,而不是扩展插件的。」)