- 新增 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 下限限定)
71 lines
3.5 KiB
Go
71 lines
3.5 KiB
Go
package gateway
|
||
|
||
import (
|
||
"time"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/services/backthink"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/urgency"
|
||
)
|
||
|
||
// 本文件把「这一轮后台思考有多紧急、是不是穿透了安静时段」变成**存档里可证**的
|
||
// 事实(切片 P4)。它刻意只读 `backthink.Record` 已有的字段 —— 本片不动
|
||
// `internal/services/backthink/**`(通用紧急度通道的落库属那片的面):
|
||
//
|
||
// - `Record.ShouldPush` 是那一轮的打扰判定结果;
|
||
// - `Record.At` 是那一轮的墙钟时间(库里存 UTC,取钟点前必须换回本地
|
||
// 时区,因为安静时段是**本地钟点**窗口);
|
||
// - 安静时段窗口取**进程生效**的 `QUIET_START_HOUR`/`QUIET_END_HOUR`
|
||
// (`config.Background`,与 backthink 的 cfg 同源:组合根把同一对值交给它)。
|
||
//
|
||
// 由 `backthink.Decide` 的判定级联(`decide.go` 的 baseDecide)可证:
|
||
//
|
||
// - 安静时段内被**放行** ⇒ 该轮重要度必然 ≥ `ImportanceHigh`(更低档位在安静
|
||
// 时段一律被扣下)⇒ 这一轮**穿透了安静时段**,档位**至少**是 `warning`
|
||
// (high)。这就是 `quietHoursBypassed` 的全部依据,不是启发式。
|
||
// - 安静时段内被**扣下** ⇒ 档位 < high ⇒ `routine`,未穿透。
|
||
// - 安静时段**之外** ⇒ 判定根本不看档位(「想不想说」由她自己的结论决定),
|
||
// 并且推送成功的行其 `PushResult.Detail` 已被投递说明覆盖 ⇒ 存档里没有任何
|
||
// 字段能反推档位。此时**不下发** urgency(面板显示「未知」),绝不猜。
|
||
//
|
||
// 因此 `AdminBackthinkItem.Urgency` 的语义是「**存档可证的档位下限**」而不是
|
||
// 「精确档位」:`warning` 读作「≥ high(含 critical)」。要做到精确档位,需要
|
||
// backthink 把每轮的 `Import` 落进 `Record`(本片不做,已在契约 §4.10(c) 与
|
||
// §7.4-17 如实登记)。
|
||
|
||
// backthinkQuietOutcome 是「该轮与安静时段的关系」的推导结果。
|
||
type backthinkQuietOutcome struct {
|
||
// urgency 是档位下限标签(urgency.Level 的词汇),无法证明时为空串。
|
||
urgency string
|
||
// bypassed 报告该轮是否穿透了安静时段(安静时段内仍被放行)。
|
||
bypassed bool
|
||
}
|
||
|
||
// backthinkQuietOutcomeOf 推导一条存档记录可证的紧急度下限与穿透标记。
|
||
//
|
||
// `at` 为零值(时间戳格式错乱的行,见 backthink.store.parseTime 的兜底)时一律
|
||
// 返回「无事实」:宁可不显示,也不把零值时间当成凌晨而谎报一轮穿透。
|
||
func backthinkQuietOutcomeOf(rec backthink.Record, startHour, endHour int) backthinkQuietOutcome {
|
||
if rec.At.IsZero() {
|
||
return backthinkQuietOutcome{}
|
||
}
|
||
if !quietHoursActiveAt(startHour, endHour, rec.At.Local()) {
|
||
return backthinkQuietOutcome{}
|
||
}
|
||
if rec.ShouldPush {
|
||
return backthinkQuietOutcome{urgency: urgency.LevelWarning.String(), bypassed: true}
|
||
}
|
||
return backthinkQuietOutcome{urgency: urgency.LevelRoutine.String()}
|
||
}
|
||
|
||
// quietHoursActiveAt 报告 at 是否落在安静时段窗口内。口径与既有三处逐字一致
|
||
// (`backthink.decide.inQuietHours`、`channelbridge` 轻思考门、
|
||
// `memory.TidyConfig.inQuietHours`):窗口是半开的 `[start, end)`,可跨午夜;
|
||
// 两端相等即关闭(`23/23` 与 `0/0` 都得到「从不生效」)。
|
||
func quietHoursActiveAt(startHour, endHour int, at time.Time) bool {
|
||
h := at.Hour()
|
||
if startHour <= endHour {
|
||
return h >= startHour && h < endHour
|
||
}
|
||
return h >= startHour || h < endHour
|
||
}
|