Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions dsh-mneme/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
# Changelog

## [Unreleased]

## 🐛 修复

- **蒸馏 JSON 崩溃窗口的 salvage(issue #339,E8 考卷)**:E8 实测 10-20% 的蒸馏窗口输出了
含记忆条目的完整 JSON 数组,只因中段一处语法错误(如杂散引号)被 `parseSummaryJsonResult`
整窗拒收——失败路径的生产行为是游标不推进 + 温度 0 重试同文同错 = 该窗口记忆静默丢失,
无任何用户可见信号;再蒸馏场景(摘要再当输入)崩溃率翻倍。修复:解析失败时做括号配对
双遍扫描(字符串感知 + 盲扫,去重合并)截出完整的顶层对象逐个 parse,救活的条目走正常
白名单校验后照常写库、推进游标;审计 `metadata.json_salvaged: true` 留痕。截出 0 条不视为
「显式空数组」,窗口保持可重试;lib-smoke 的三类退化输入行为不变。

## 🆕 新增

- **`dreamMergeGuard`(issue #339,E8 考卷,opt-in 默认关)**:巩固 merge 护栏——合并对象命中
长保留类型(与 archive 护栏同表:preference/pattern/rejected_solution/constraint/pitfall)的
merge 决策整条跳过。E8 实测巩固损耗里 10/26 条被丢约束已归位 guarded 类型仍被 merge 吃掉
(archive 护栏只挡 archive 不挡 merge,「更精炼的摘要」恰是约束失真的主通道)。
`dreamSkipInvalid`(默认开)时被跳条目进 `dream_runs.skipped`、run 记 degraded;关闭时整单拒绝。
dream 与 sleep 两条链路同一判据;白名单 + 计数锁 +1。

### scope

- **A2 软加权补齐到注入通道(issue #339,E7 考卷)**:此前 ×0.5/×1.25 只作用于 searchMemories,
E7 实测「explicit 标注 + 软档」的注入集与无标注逐条相同(80/80)——自动注入这个主泄露面上
软档形同虚设,而文档本就承诺「关闭时全部为软隔离」。现 `scopeEnabled` 开启且会话至少一维
可解析时,注入规则路比较器在层内数值积乘 `scopeMultiplier`(priority 档位不动)、selectiveInject
相似度重排乘同乘数;未激活时乘 1,排序与改动前逐字节一致。strictScope 硬过滤保持在软加权
之前,与检索侧叠加顺序一致。

## [0.8.12] - 2026-10-01

## 🐛 修复
Expand All @@ -17,6 +47,31 @@
## 🧹 工程

- **基线持久化回归集 9 条,全量测试 1441 → 1450**:无种子对照组与播种组同库同写入(一个仍触发、一个被拦下——锁的正是这个差异,否则播种被忽略时两条断言同真同假)、阈值照常累积(+10 条仍触发)、run 自报基线优先于现场计算(运行后再长 15 条仍不触发)、成功轮落库 / 失败轮不落、sleep 行不进 auto 基线、半个基线被拒、老库补列幂等、非法种子(半个 / 负数 / 非整数 / 非对象)一律按「没有基线」处理、收尾取证失败既不反噬审计行也不写假基线、`index.js` 接线源码锁。**变异检验**:把播种改成忽略种子(`toBaseline(baselineSeed)` 恒为空)→ 2 条变红;把「成功轮落基线」分支短路 → 1 条变红。
- **蒸馏 JSON 崩溃窗口的 salvage(issue #339,E8 考卷)**:E8 实测 10-20% 的蒸馏窗口输出了
含记忆条目的完整 JSON 数组,只因中段一处语法错误(如杂散引号)被 `parseSummaryJsonResult`
整窗拒收——失败路径的生产行为是游标不推进 + 温度 0 重试同文同错 = 该窗口记忆静默丢失,
无任何用户可见信号;再蒸馏场景(摘要再当输入)崩溃率翻倍。修复:解析失败时做括号配对
双遍扫描(字符串感知 + 盲扫,去重合并)截出完整的顶层对象逐个 parse,救活的条目走正常
白名单校验后照常写库、推进游标;审计 `metadata.json_salvaged: true` 留痕。截出 0 条不视为
「显式空数组」,窗口保持可重试;lib-smoke 的三类退化输入行为不变。

## 🆕 新增

- **`dreamMergeGuard`(issue #339,E8 考卷,opt-in 默认关)**:巩固 merge 护栏——合并对象命中
长保留类型(与 archive 护栏同表:preference/pattern/rejected_solution/constraint/pitfall)的
merge 决策整条跳过。E8 实测巩固损耗里 10/26 条被丢约束已归位 guarded 类型仍被 merge 吃掉
(archive 护栏只挡 archive 不挡 merge,「更精炼的摘要」恰是约束失真的主通道)。
`dreamSkipInvalid`(默认开)时被跳条目进 `dream_runs.skipped`、run 记 degraded;关闭时整单拒绝。
dream 与 sleep 两条链路同一判据;白名单 + 计数锁 +1。

### scope

- **A2 软加权补齐到注入通道(issue #339,E7 考卷)**:此前 ×0.5/×1.25 只作用于 searchMemories,
E7 实测「explicit 标注 + 软档」的注入集与无标注逐条相同(80/80)——自动注入这个主泄露面上
软档形同虚设,而文档本就承诺「关闭时全部为软隔离」。现 `scopeEnabled` 开启且会话至少一维
可解析时,注入规则路比较器在层内数值积乘 `scopeMultiplier`(priority 档位不动)、selectiveInject
相似度重排乘同乘数;未激活时乘 1,排序与改动前逐字节一致。strictScope 硬过滤保持在软加权
之前,与检索侧叠加顺序一致。

- **宿主开发依赖对齐 0.2 线(PR #344)**:`peerDependencies` 自 #121 起就声明覆盖 0.2.x,`devDependencies` 却停在 `^0.1.0-rc.6`(0.x 语义 = `>=0.1.0-rc.6 <0.2.0`,永远装不到 0.2 线)——**声明支持 0.2、CI 却从没跑过 0.2**,而桌面端 DSH NEXT 内置内核已是 0.2.0-rc.1。开发依赖对齐 0.2.0-rc.1,lockfile 与 CI 真落在声明覆盖的版本上。
- **brace-expansion 钉到 5.0.12(PR #345,CVE-2026-102276/102277/102278)**:osv-scanner 三条告警(两条 high 栈耗尽 DoS + 一条 medium 二次时间 DoS)全落在 devDependency 链的同一包上——实际打不着(不被仓库自身 import),但 lock 合规仍要清;overrides 抬 pin 号即清零,`npm audit` 双口径 0 vulnerabilities。
Expand Down
2 changes: 1 addition & 1 deletion dsh-mneme/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ dsh web
| `searchSemanticDedupThreshold` | `0.95` | 语义去重相似度阈值(v0.5.0,默认 0.95,范围 0.5-1.0):`searchSemanticDedup=true` 时生效,调整可防小模型误折叠 |
| `memoryQualityFilter` | `{enabled:true, archiveThreshold:30, degradeThreshold:60, minContentLength:10, exemptImportance:4}` | 记忆质量过滤(v0.4.6,默认开):写库前启发式打分 0-100,元记忆词汇/自指/过短/重复/近似重复扣分;≥60 正常存储,30-60 降权(注入排序按 importance×quality/100),<30 归档标记 `low_quality`(显式搜索仍可召回,永不自动注入;`exemptImportance` 豁免:importance ≥ 该值只降权不归档,#135) |
| `llmAudit` | `{enabled:true, retentionDays:90}` | LLM 消耗审计(v0.4.6,默认开):每次后台 LLM 调用(`autoDream` / `autoSummarize` / `sleep` / `entityExtract`,对应 `operation_type` 为 `dream_consolidate` / `dream_summarize` / `dream_narrative`、`summarize_compress`、`sleep_conflict` / `sleep_pattern`、`entity_extract`)写 `llm_audit_logs`(tokens/duration/status/source);失败记 error 不阻塞;只读 API `/api/dsh-mneme/semantic/llm-audit` + `/llm-audit/stats` |
| `scopeEnabled` | `false` | 作用域隔离总开关(v0.8.0,issue #17):memory_save 按会话身份写入 agent / workspace 标注(agentPreset / 工作区路径,registry 反查取不到回退 header.cwd,再取不到 NULL);去重键扩展含作用域三元——跨作用域同标题不再物理合并;检索按当前会话作用域加权(命中 ×1.25、他 scope ×0.5 保留可见)。也走 feature_flags 白名单(面板可启停) |
| `scopeEnabled` | `false` | 作用域隔离总开关(v0.8.0,issue #17):memory_save 按会话身份写入 agent / workspace 标注(agentPreset / 工作区路径,registry 反查取不到回退 header.cwd,再取不到 NULL);去重键扩展含作用域三元——跨作用域同标题不再物理合并;检索与注入排序按当前会话作用域加权(命中 ×1.25、他 scope ×0.5 保留可见,issue #339 补齐注入通道)。也走 feature_flags 白名单(面板可启停) |
| `strictScope` | `false` | 作用域硬过滤(v0.8.0 引入,v0.8.1 起只认显式声明;依赖 `scopeEnabled`):检索 / 注入 / 列表 / 单取四路过滤,**显式声明**收窄到他者作用域的记忆完全不可见;载体自动标注只降权保留可见——真正的物理隔离请用 sensitivity。会话身份解析不到时 fail-closed 只挡显式行。关闭时全部为软隔离(降权保留可见)。也走 feature_flags 白名单 |
| `writeAdmission` | `{enabled:false, enforce:false}` | 写入准入(issue #254,默认关):把「这条该不该进库」前移到 LLM 之前。第 1 级是零 LLM 的确定性判据——空白 / 纯噪声 + 密钥 / PII(判据来源是 #164 A2,经 `sensitiveScan` 注入,本批只定义接口)。`enabled` 跑判据并落审计行(`llm_audit_logs` 的 `metadata.deny`),`enforce` 才真的拒(`store.save` 之前返回,`memory_save` 返回 `action:"denied"` + `reason`);只开 `enabled` = 仅告警、写入不拦。去重键命中不进第 1 级(归 write-update 放行),G1/G2 阈值只计量。也走 feature_flags 白名单 |
| `conflictFreezeEnabled` | `false` | 冲突冻结(v0.4.4):dream 发现矛盾对不自动裁决,冻结进冲突队列;状态页「冲突队列」支持并排对比与人工确认(保留 A / 保留 B / 仅标记已处理,v0.8.0) |
Expand Down
5 changes: 3 additions & 2 deletions dsh-mneme/docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@

| 键 | 默认 | 作用 | 开启后果 / 冲突 |
|---|---|---|---|
| `scopeEnabled` | `false` | 存储层总开关:写入标注 agent_scope / workspace_scope,去重键扩展 | opt-in;关 = 写入不标注、行为与 A1 前逐字节一致 |
| `strictScope` | `false` | 硬过滤模式(A3):他 scope 完全不可见、未标注恒可见(fail-closed) | 依赖 `scopeEnabled` 打开才有意义;关 = A2 软隔离(他 scope 降权保留可见) |
| `scopeEnabled` | `false` | 存储层总开关:写入标注 agent_scope / workspace_scope,去重键扩展;检索与注入排序按当前会话作用域加权(命中 ×1.25、他 scope ×0.5 保留可见,issue #339 补齐注入通道) | opt-in;关 = 写入不标注、行为与 A1 前逐字节一致 |
| `strictScope` | `false` | 硬过滤模式(A3):他 scope 完全不可见、未标注恒可见(fail-closed);硬过滤先于软加权执行 | 依赖 `scopeEnabled` 打开才有意义;关 = A2 软隔离(他 scope 降权保留可见,检索与注入同权重) |

## 审计

Expand Down Expand Up @@ -118,6 +118,7 @@
| `dreamMinExplicitCoverage` | `0.5` | 显式决策覆盖率下限(0–1) | 防截断输出被隐式 keep 洗白 |
| `dreamSkipInvalid` | `true` | 跳过单条非法决策、应用合法子集、run 记 degraded(#89) | `false` = 恢复整单拒绝 |
| `allowCrossTypeMerge` | `false` | 放宽跨类型合并检查 | opt-in;`dreamSkipInvalid` 关时跨类型 merge 直接整单拒绝 |
| `dreamMergeGuard` | `false` | guarded 类型 merge 护栏(#339/E8):合并对象命中长保留类型(与 archive 护栏同表:preference/pattern/rejected_solution/constraint/pitfall)的 merge 决策整条跳过——E8 实测巩固损耗里 10/26 条被丢约束已归位 guarded 类型仍被 merge 吃掉 | opt-in;`dreamSkipInvalid`(默认开)时被跳条目进 `dream_runs.skipped`、run 记 degraded,关闭时整单拒绝 |
| `dreamNarrativeEnabled` **light** | `false` | dream 期间按共享 tag 聚类合成叙述条(#164 对齐) | opt-in;按需检索、不常驻注入 |
| `dreamNarrativeMinCluster` | `3` | 成簇门槛(共享同一 tag 的记忆数,2–20) | — |
| `documentMemoryEnabled` **light** | `false` | document 型记忆:长文档指针行,全文归 agent(#230) | opt-in;注册校验 + C2 去重 + supersede 记账 |
Expand Down
11 changes: 10 additions & 1 deletion dsh-mneme/lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,14 @@ export const Config = z.object({
// 显式开启后放宽跨类型合并检查(类型边界由用户自行承担);配合
// dreamSkipInvalid 理解:关闭 skipInvalid 时跨类型 merge 直接整单拒绝。
allowCrossTypeMerge: z.boolean().default(false),
// issue #339 / E8 考卷:merge 护栏(opt-in,默认关 = 现状)。开启后合并对象
// 命中长保留类型(preference/pattern/rejected_solution/constraint/pitfall,
// 与 archive 护栏同一张表)的 merge 决策整条跳过——E8 实测巩固损耗里
// 10/26 条被丢约束已归位 guarded 类型仍被 merge 吃掉,而 archive 护栏只挡
// archive 不挡 merge,「更精炼的摘要」恰是约束失真的主通道。skipInvalid
// (默认开)时被跳条目进 dream_runs.skipped、run 记 degraded;关闭
// skipInvalid 时整单拒绝。与 allowCrossTypeMerge 同用时长 guard 先判。
dreamMergeGuard: z.boolean().default(false),
// Rule version for dream adjudication: when this bumps, older dream_runs
// degrade to historical evidence (their receipts no longer drive live
// decisions). Default 0 = no versioning in use yet.
Expand Down Expand Up @@ -714,7 +722,8 @@ export const Config = z.object({
// agent_scope, workspace_scope, sensitivity)。检索侧加权/过滤在 A2/A3 落地。
// 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
scopeEnabled: z.boolean().default(false),
// strictScope(A3):硬过滤模式。关闭=A2 软隔离(他 scope 降权保留可见);
// strictScope(A3):硬过滤模式。关闭=A2 软隔离(他 scope 降权保留可见,
// 检索与注入排序同权重,issue #339 补齐注入通道);
// 开启后检索/注入/list/get 按 issue #17 四象限可见性公式硬过滤——带他 scope
// 的记忆完全不可见,未标注(NULL)恒可见;当前会话某维度解析不到时该维度
// 带标注的记忆一律不可见(fail-closed:身份不明只见全局)。依赖 scopeEnabled
Expand Down
4 changes: 3 additions & 1 deletion dsh-mneme/lib/dream.js
Original file line number Diff line number Diff line change
Expand Up @@ -1181,7 +1181,9 @@ export function createDreamScheduler({ onRun, thresholdCount = 10, thresholdChar
// Issue #89:v0.6.9(Issue #26)的宽容路径在 v0.7.11 重写中丢失——单条
// 非法决策重新只跳过该条、合法子集照常应用(run 记为 degraded)。
skipInvalid: config.dreamSkipInvalid !== false,
allowCrossTypeMerge: config.allowCrossTypeMerge === true
allowCrossTypeMerge: config.allowCrossTypeMerge === true,
// issue #339 / E8:guarded 类型 merge 护栏(opt-in,dreamMergeGuard)。
mergeGuard: config.dreamMergeGuard === true
});
// Issue #135:模型把 UUID 缩写成前缀时,唯一前缀已在校验前被解析回完整 id。
// 解析量是模型输出质量的一个直接信号(>0 意味着模型在缩写 id),记一条 info
Expand Down
16 changes: 16 additions & 0 deletions dsh-mneme/lib/dream/decisions.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,22 @@ export function validateDecisions(decisions, snapshot, options = {}) {
if (mergeTypes.size > 1 && options.allowCrossTypeMerge !== true) {
local.push(`${at}: merge ids span multiple types (${[...mergeTypes].join(", ")})`);
}
// issue #339 / E8 考卷:merge 护栏(opt-in,dreamMergeGuard)。巩固损耗
// 实测里 10/26 条被丢约束已归位 guarded 类型仍被 merge 吃掉——archive
// 护栏(上方)只挡 archive 不挡 merge,而 merge 的「更精炼摘要」恰是
// 约束失真的主通道。开启后被合并对象命中 ARCHIVE_GUARDED_TYPES 的
// merge 决策整条跳过(与 archive 护栏同款通道:skipInvalid 时 skipped
// 明细、严格模式整单拒绝)。
if (local.length === 0 && options.mergeGuard === true) {
const guardedTypes = new Set();
for (const id of d.ids) {
const mem = snapshot.get(id);
if (mem && ARCHIVE_GUARDED_TYPES.has(mem.type)) guardedTypes.add(mem.type);
}
if (guardedTypes.size > 0) {
local.push(`${at}: merge of long-retention type(s) ${[...guardedTypes].join(", ")} is blocked by dreamMergeGuard`);
}
}
}
}
if (local.length > 0) {
Expand Down
3 changes: 3 additions & 0 deletions dsh-mneme/lib/dream/sleep.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,9 @@ async function phaseConflicts(ctx, service, config, logger, runId, semantic = nu
minAgeHours: config.reflectionUpdateMinAgeHours,
skipInvalid: config.dreamSkipInvalid !== false,
allowCrossTypeMerge: config.allowCrossTypeMerge === true,
// issue #339 / E8:guarded 类型 merge 护栏(opt-in,dreamMergeGuard),
// 与 dream 主链路同一判据。
mergeGuard: config.dreamMergeGuard === true,
// Issue #126 review(Copilot):默认档必须真的只放行旧动作集——只换 prompt 挡不住
// 模型自发输出 supersede / differentiate,那样"opt-in 零行为变化"就不成立。
allowedActions: actionSet === "full" ? null : LEGACY_ACTIONS
Expand Down
5 changes: 3 additions & 2 deletions dsh-mneme/lib/inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,9 @@ export function createInjector(ctx, service, settings, config) {
const query = lastUserQuery(ctx);
if (query) prefetchQueryVector(query);
const queryVector = queryVectorCache.get(query);
// v0.8.0 A3:scope 随请求解析(strict 开启时 injectCandidates 内硬过滤;
// flag 关闭时解析器返回 null,注入行为不变)。
// v0.8.0 A3:scope 随请求解析(解析器恒返回对象,身份取不到时两维为
// null);硬过滤与软加权的门控都在 injectCandidates 内——strict 看
// strictScope,软加权看 scopeEnabled 且至少一维可解析(issue #339)。
const scope = resolveSessionScope(ctx);
// Issue #205:跨轮轮换——最近 N 个查询轮次注入过的 id 本轮不再优先。
const sessionId = ctx?.agent?.session?.id ?? "_";
Expand Down
Loading
Loading