fix(core): 规则结论改在脱敏前算,会话路上 fabric.loader_0_12 能命中(M241)
会话 JSONL 是写前脱敏的,而脱敏的「≥64 无点号不透明串」是有损的: fabric.loader_0_12 唯一的证据行(Sponge mixin 类路径 66 字符)被抹成 <redacted:opaque>,规则在会话路上永不命中。 修法不动脱敏策略、也不往盘上多写一份原文: 1. SessionWriter 在内存里留一个 2000 行的原始尾窗(不落盘); 2. 收尾时把这些**原始**事实交给新的 diagnose::session::conclude 跑规则, 产出契约形状的 diagnosis payload —— 它落盘前仍过 Redactor::scrub_json; 3. 日志页优先读这条落盘结论,老会话才回落到「在脱敏文本上重跑规则」。 为什么不是「原文与脱敏文本并存」:会话 JSONL 里存原文会踩 core/tests/launch/pipeline_cases.rs 早已钉死的「事件流不得含凭据明文」。 顺带把 EventKind 纯搬移到 launch/session/event_kind.rs 守 400 行上限。
This commit is contained in:
1 parent
74be510114
commit
edd6402fd5
10 files changed
+633
-65
No files matched your search
@@ -28,6 +28,8 @@ use crate::dto::{
|
||||
|
||||
use super::env::HostEnv;
|
||||
|
||||
#[path = "logs/diagnosis.rs"]
|
||||
mod diagnosis;
|
||||
#[path = "logs/location.rs"]
|
||||
mod location;
|
||||
|
||||
@@ -141,30 +143,43 @@ pub fn line_of(event: &Value) -> Option<LogLine> {
|
||||
})
|
||||
}
|
||||
|
||||
/// 整个事件流 → 日志行(解析失败的行跳过,容忍被 kill 时截断的尾行)。
|
||||
pub fn read_lines(path: &Path) -> Vec<LogLine> {
|
||||
/// 整个事件流的原始 JSON 行(解析失败的行跳过,容忍被 kill 时截断的尾行)。
|
||||
///
|
||||
/// 一次读完所有事件:日志行与诊断结论都从这同一份读里取,不重复读盘。
|
||||
pub fn read_events(path: &Path) -> Vec<Value> {
|
||||
let Ok(text) = std::fs::read_to_string(path) else {
|
||||
return Vec::new();
|
||||
};
|
||||
text.lines()
|
||||
.filter(|line| !line.trim().is_empty())
|
||||
.filter_map(|line| serde_json::from_str::<Value>(line).ok())
|
||||
.filter_map(|event| line_of(&event))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 整个事件流 → 日志行(**展示面**:`payload.line`,即写盘前已脱敏的文本)。
|
||||
pub fn read_lines(path: &Path) -> Vec<LogLine> {
|
||||
read_events(path).iter().filter_map(line_of).collect()
|
||||
}
|
||||
|
||||
/// 打开一个会话:日志行 + 规则引擎结论。
|
||||
///
|
||||
/// 结论优先取会话里**落盘的那份**(写会话时用原始行算过,见 [`diagnosis`] 模块文档);
|
||||
/// 老会话没有它,才回落到「在脱敏文本上重跑规则」。
|
||||
pub fn open(env: &HostEnv, key: &str) -> (Vec<LogLine>, Option<DiagnosisReport>) {
|
||||
let Some(entry) = find(env, key) else {
|
||||
return (Vec::new(), None);
|
||||
};
|
||||
let path = event_path(env, &entry);
|
||||
let lines = read_lines(&path);
|
||||
let diagnosis = diagnose_lines(&entry, &lines);
|
||||
let events = read_events(&event_path(env, &entry));
|
||||
let lines: Vec<LogLine> = events.iter().filter_map(line_of).collect();
|
||||
let diagnosis = diagnosis::stored(&entry, &events).or_else(|| diagnose_lines(&entry, &lines));
|
||||
(lines, diagnosis)
|
||||
}
|
||||
|
||||
/// 规则引擎跑一遍(**规则先跑,模型补位**:这里只出规则结论)。
|
||||
/// 在**脱敏文本**上重跑规则引擎(老会话的兜底路径)。
|
||||
///
|
||||
/// ⚠️ 这条路的证据是「写盘前脱敏过的」行:`≥64` 无点号长串(Sponge 类路径、长 hash)会被
|
||||
/// 抹成 `<redacted:opaque>`,依赖那些字面量的规则(`fabric.loader_0_12`)在这里**不可能命中**。
|
||||
/// 新会话通常不必走这条路——写会话时用原始行算过结论,见 [`diagnosis::stored`]。
|
||||
pub fn diagnose_lines(entry: &SessionIndexEntry, lines: &[LogLine]) -> Option<DiagnosisReport> {
|
||||
let stderr_text: String = lines
|
||||
.iter()
|
||||
@@ -355,6 +370,9 @@ pub fn session_finished(env: &HostEnv, session: &str) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "logs/m241_tests.rs"]
|
||||
mod m241_tests;
|
||||
#[cfg(test)]
|
||||
#[path = "logs_tests.rs"]
|
||||
mod tests;
|
||||
Reference in new issue
Block a user