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

@@ -1,12 +1,13 @@
import type { BackthinkItem } from "../../lib/types";
/**
* 后台思考行「紧急度 / 穿透安静时段」标记的**纯逻辑**(契约 v0.68 §4.10(b))。
* 后台思考行「紧急度 / 穿透安静时段」标记的**纯逻辑**(契约 v0.70 §4.10(b))。
*
* 后端 `urgency` 的语义是**存档可证的档位下限**(`warning` = 至少 high 档,可能更
* 高),不是精确档位;而窗口之外的轮次存档里没有档位,键**缺席**。因此本层:
* - 缺席 ⇒ 显示「档位未知」(**不是**「例常」——把未知画成事实就是这次要避免的坑);
* - 未知取值 ⇒ 原样显示后端给的词,不猜一个更好听的;
* 三种态必须画得**不一样**,否则就是把猜当事实:
* - **精确档位**:后端 `urgency` 就是该轮实际生效的档位(随记录落库,`urgencyLowerBound` 缺席);
* - **≥ 下限**:`urgencyLowerBound:true` ⇒ 该行没有落库档位(旧行 / 未走到档位判定点的行),
* 此值由安静时段窗口规则推导:`≥ 高` = 至少 high、可能更高;`≥ 例常` = 只确定不低于最低档;
* - **未知**:`urgency` 键缺席 ⇒ 存档里没有这个事实(**不是**「例常」)。
* - `quietHoursBypassed` 只在 true 时渲染「穿透安静时段」。
*/
@@ -15,40 +16,62 @@ export type UrgencyTagTone = "ok" | "warn" | "err" | "neutral" | "accent";
export interface UrgencyTag {
label: string;
tone: UrgencyTagTone;
/** 悬停说明(为什么是这个档位 / 为什么未知),给人看的口径。 */
/** 悬停说明(为什么是这个档位 / 为什么只是下限 / 为什么未知),给人看的口径。 */
hint: string;
}
/** 档位未知时的标签(键缺席 = 后端没有这个事实)。 */
export const URGENCY_UNKNOWN_LABEL = "档位未知";
/** 下限标签的前缀:推导值一律带它,保证与精确档位**一眼可辨**。 */
export const URGENCY_LOWER_BOUND_PREFIX = "≥ ";
/** 穿透安静时段的标签。 */
export const QUIET_HOURS_BYPASS_LABEL = "穿透安静时段";
/** urgencyTag 把后端 `urgency` 映射成标签;键缺席时给「档位未知」。 */
export function urgencyTag(item: Pick<BackthinkItem, "urgency">): UrgencyTag {
/** 精确档位的标签:后端 `urgency` 词汇 → 人话。 */
const EXACT_LABEL: Record<string, string> = {
routine: "例常",
warning: "紧急(高)",
critical: "紧急·安全域",
};
/** 精确档位的色调(安全域 err / 高 warn / 其余中性)。 */
function exactTone(level: string): UrgencyTagTone {
if (level === "critical") return "err";
if (level === "warning") return "warn";
return "neutral";
}
/** 下限的悬停说明:点明它是怎么推出来的、以及为什么它**不是**精确值。 */
const LOWER_BOUND_HINT =
"这一行没有存档档位(档位列出现之前的旧记录,或从未走到档位判定点的行)⇒ 精确档位已无从得知," +
"此值是安静时段窗口规则推导出的下限:窗口内被放行 ⇒ 至少 high(可能是安全域);" +
"被扣下 ⇒ 只确定不低于最低档。不是精确档位。";
/** urgencyTag 把后端 `urgency` + `urgencyLowerBound` 映射成标签。 */
export function urgencyTag(item: Pick<BackthinkItem, "urgency" | "urgencyLowerBound">): UrgencyTag {
const level = item.urgency;
switch (level) {
case "routine":
return { label: "例常", tone: "neutral", hint: "安静时段内被扣下的轮次:档位低于 high,不穿透" };
case "warning":
return {
label: "紧急(≥ 高)",
tone: "warn",
hint: "安静时段内仍被放行 ⇒ 档位至少 high;记录里无法区分 high 与 critical,故显示下限",
};
case "critical":
return { label: "紧急·安全域", tone: "err", hint: "安全域事件(可穿透工作/忙碌与安静时段)" };
case undefined:
case "":
return {
label: URGENCY_UNKNOWN_LABEL,
tone: "neutral",
hint: "这一轮的档位在存档里不可证(轮次不在安静时段窗口内,记录不保存档位)——不是「例常」",
};
default:
return { label: level, tone: "neutral", hint: "后端给了本面板不认识的档位,原样显示" };
if (level === undefined || level === "") {
return {
label: URGENCY_UNKNOWN_LABEL,
tone: "neutral",
hint: "这一轮的档位在存档里不存在(该行没有落库档位,且不在安静时段窗口内 —— 窗口之外判定不看档位)——不是「例常」",
};
}
if (item.urgencyLowerBound === true) {
return {
// 未知取值原样显示(后端加了本面板不认识的档位时不猜一个更好听的词),但下限前缀照加。
label: URGENCY_LOWER_BOUND_PREFIX + (EXACT_LABEL[level] ?? level),
tone: exactTone(level),
hint: LOWER_BOUND_HINT,
};
}
const exact = EXACT_LABEL[level];
if (exact === undefined) {
return { label: level, tone: "neutral", hint: "后端给了本面板不认识的档位,原样显示" };
}
return { label: exact, tone: exactTone(level), hint: `该轮实际生效的档位(精确值):${level}` };
}
/** quietHoursBypassTag 只在后端确实标记了穿透时返回标签。 */