feat(gateway)!: 后台思考档位精确化(+ 面板三态 + 契约 v0.69/v0.70)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- gateway:优先读落库的 rec.Urgency(精确档位,与安静时段窗口无关);仅空串才回落到既有下限推导,并打 urgencyLowerBound 标记(**只在推导值上为 true**)
- 面板三态:精确(例常/紧急(高)/紧急·安全域)|下限(一律加 ≥ 前缀)|未知;下限悬停说明写清「为什么旧行只能是下限」
- 实时帧零协议改动:Record 的 JSON 键就是 urgency ⇒ 帧自然带上;wsParse 早已防御性读取(只加一处 urgencyLowerBound 防御读)
- 契约:v0.69(directMsgPerMin 透传修复,逐字照抄附录 A)与 v0.70(精确化 + 更正 §4.10(b)「实时帧不带这两键」)一起登记
- 变异实测:旧行不标下限 ⇒ 3 条测试红(结构体对比);前端去掉 ≥ ⇒ 渲染测试红
- ⚠️ 诚实边界(§7.3 已登记):实时旧记录刷新前显示「档位未知」、刷新后显示「≥ 例常」——同一条记录两路下的已知差异
This commit is contained in:
AskaEth committed 2026-10-05 14:15:55 +08:00
1 parent f9c5b56a2f
commit a0abf0199a
11 files changed
+390 -131

No files matched your search

+11 -5
View File
@@ -45,12 +45,18 @@ export interface BackthinkItem {
/** 事件驱动推送绕过了限频闸(后端 PushResult.Bypassed,"该发就发")。
* ⚠️ 与 `quietHoursBypassed`(安静时段穿透)**是两件事**:这个说的是限频门。 */
bypassed?: boolean;
/** 该轮紧急度的**存档可证下限**(后端 `urgency`,`internal/urgency` 档位词汇:
* `routine` | `warning` | `critical`)。`warning` 读作「至少 high 档(可能更高)」。
* 缺席 = 后端没有这个事实(轮次不在安静时段窗口内时无从反推),**不是**「例常」。
* ⚠️ 实时帧走的是 bus 上原始的 `backthink.Record`,它目前**不带**档位 ⇒ 实时行显示
* 「档位未知」,重开页面由历史拉取(`GET /admin/backthink`)补齐。 */
/** 该轮紧急度档位(后端 `urgency`,`internal/urgency` 档位词汇:
* `routine` | `warning` | `critical`)。**默认是精确档位**(该轮实际生效的档位,随记录落库);
* 与 `urgencyLowerBound:true` 同时出现时才是**推导出的下限**(`≥ 高` = 至少 high、可能更高)。
* 缺席 = 后端没有这个事实,**不是**「例常」。
* ⚠️ 实时帧走的是 bus 上原始的 `backthink.Record`,自 X2a 起该记录带上 `urgency`(同样是
* 精确值);但 `urgencyLowerBound` / `quietHoursBypassed` 只由历史拉取(`GET /admin/backthink`)
* 下发(后两者是网关按安静时段窗口推导的,不在记录里)。 */
urgency?: string;
/** 后端标记「`urgency` 只是下限」时为 true:该行没有落库档位(`urgency` 列出现之前的旧行,
* 或从未走到档位判定点的行),值由安静时段窗口规则反推。后端只在为 `true` 时下发该键;
* 缺席(且 `urgency` 存在)= 精确档位。**不得把带此标记的值显示成精确档位。** */
urgencyLowerBound?: boolean;
/** 该轮**穿透了安静时段**(轮次时间落在安静时段窗口内且该轮判定要推送)。后端只在
* `true` 时下发该键;缺席 = 没有这件事,**不等于**「确认没穿透」。 */
quietHoursBypassed?: boolean;
+7 -4
View File
@@ -301,12 +301,14 @@ export function parseBackthinkItem(v: unknown): BackthinkItem | null {
const detail = firstPushText(pr.Detail, pr.detail, r.detail, r.push_detail);
const bypassedRaw = pr.Bypassed ?? pr.bypassed ?? r.bypassed ?? r.push_bypassed;
const bypassed = typeof bypassedRaw === "boolean" ? bypassedRaw : undefined;
// v0.68 的档位/穿透:**防御性读取**(兼容 camelCase 与 Go 字段名)。当前 bus 上发布的
// 是原始 `backthink.Record`,它**不带**这两个字段 ⇒ 今天恒为 undefined(本次会话新到
// 的行显示「档位未知」,重开页面由历史拉取补真值)。一旦后端把这两个事实放进帧里,
// 这里无需改动就会显示出来 —— 但绝不因为「帧里没有」就编一个值。
// v0.68 的档位/穿透:**防御性读取**(兼容 camelCase 与 Go 字段名)。X2a 起 bus 上发布的
// 原始 `backthink.Record` 已带 `urgency`(该轮实际生效的**精确**档位)⇒ 实时行同样显示档位;
// `urgencyLowerBound` / `quietHoursBypassed` 不在记录里(由网关按安静时段窗口推导),
// 这里照旧防御性读取,一旦帧里带上就无需改动地显示出来 —— 但绝不因为「帧里没有」就编一个值。
const urgencyRaw = r.urgency ?? r.Urgency ?? r.urgency_level ?? r.urgencyLevel;
const urgency = typeof urgencyRaw === "string" && urgencyRaw ? urgencyRaw : undefined;
const lbRaw = r.urgencyLowerBound ?? r.UrgencyLowerBound;
const urgencyLowerBound = typeof lbRaw === "boolean" ? lbRaw : undefined;
const qhRaw = r.quietHoursBypassed ?? r.QuietHoursBypassed;
const quietHoursBypassed = typeof qhRaw === "boolean" ? qhRaw : undefined;
return {
@@ -319,6 +321,7 @@ export function parseBackthinkItem(v: unknown): BackthinkItem | null {
detail,
bypassed,
urgency,
urgencyLowerBound,
quietHoursBypassed,
at,
};