@furongjun1999/dsh-memory 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/codebuddy/CODEBUDDY.md +5 -5
- package/docs/eval//345/217/221/345/270/20309_/346/243/200/347/264/242/351/235/242/344/270/211/346/211/271/346/224/266/345/217/243_v1.0.md +1 -1
- package/docs/eval//345/217/221/345/270/20310_/350/257/273/351/235/242/346/215/237/345/235/217UTF8/345/256/266/346/227/217/346/224/266/345/217/243_v1.0.md +71 -0
- package/docs//345/267/245/344/275/234/347/272/252/345/276/213_/350/256/244/347/237/245/345/233/276/346/235/241/347/233/256_v1.1.json +4 -4
- package/md_cg/bench6_arms.py +10 -6
- package/md_cg/census.py +2 -1
- package/md_cg/mdcg.py +83 -25
- package/md_cg/mdcos.py +9 -3
- package/md_cg/mreview/locate.py +1 -1
- package/md_cg/rollback.py +12 -3
- package/md_cg/routing.py +10 -1
- package/md_cg/test_corrupt_utf8_read_surfaces.py +451 -0
- package/md_cg/test_generation_guard.py +15 -9
- package/md_cg/test_health_corrupt_utf8.py +37 -0
- package/md_cg/test_index_crossprocess_reload.py +39 -24
- package/md_cg/test_issue53_cipher_health_hash.py +385 -0
- package/package.json +1 -1
- package/skills/plugin.json +1 -1
- package/zcode/AGENTS.md +5 -5
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|

|
|
15
15
|
|
|
16
|
-
**高性能 · 无幻觉 · 多智能体适用的长期记忆系统(v0.7.
|
|
16
|
+
**高性能 · 无幻觉 · 多智能体适用的长期记忆系统(v0.7.2)**
|
|
17
17
|
|
|
18
18
|
</div>
|
|
19
19
|
|
|
@@ -455,7 +455,8 @@ AEIS 仅作可选「身体」能力后端(角色扮演生成),不再存记
|
|
|
455
455
|
| 文档 | 内容 |
|
|
456
456
|
|---|---|
|
|
457
457
|
| **[docs/ 目录索引](docs/README.md)** | 六域快速索引(mdcg / swarm / hive / theory / eval / plans)· 新文档归域规则 |
|
|
458
|
-
| **[Release v0.7.
|
|
458
|
+
| **[Release v0.7.2](https://github.com/FuRongJun-1999/dsh-memory/releases/tag/v0.7.2)** | 本版变更:读面损坏 UTF-8 家族收口——① 读取面不再因损坏非 UTF-8 节点崩溃(贡献者 PR #54)+同形态残余加固(census / restore / read_preimage / locate 四站点:坏 trash 与坏前像均结构化失败且源保留,health 增 skipped_unreadable 计数)② issue #53 三缺陷(密文假域标签 / 桶计数虚高 / 假 hash_drift)③ 工程面收口(PR #47:CI 先构建 Rust/Hive 二进制再跑 Python 全量;套件并行改串行防共享临时目录竞态;charset 表与纪律指纹跨平台同判) |
|
|
459
|
+
| **[Release v0.7.1](https://github.com/FuRongJun-1999/dsh-memory/releases/tag/v0.7.1)** | 本版变更:检索面三批同族收口——① issue #52 条件先行与截断可观测(timeline/anchors/consistency 的索引序切片退场:条件先于限额、截断可观测+可操作提示,max_scan 数值不动)② stg 结构索引(by_session/by_layer/by_time 内存倒排+条件资格首验,双 flag 默认关,在役实测 52× 收敛(7607 条规模;加速比随库规模与条件收窄度增长、小库下显著降低)、两臂逐位等价)③ consistency 选面(MAX_SCAN=200 索引序静默截断退场:相关性预筛+保底面+面内例外+truncated/kept/hint 可观测,数值一字未动) |
|
|
459
460
|
| **[Release v0.7.0](https://github.com/FuRongJun-1999/dsh-memory/releases/tag/v0.7.0)** | 本版变更:① **自迭代与睡眠**——睡眠周期引擎(影子副本迭代 + 语义四闸 + git 周期合并,九步显式台账,缺省 23:00-07:00 窗口、一小时一轮、可调)② **因果/时间图检索路**进默认检索(因果路复用既有条件链;时间路走唯一时间核,半衰期 30 天;边类型集可配)③ **六要素补成 6 行索引角色**(验证方式/不适用条件进默认检索,边界命中与资格裁决分开计数)④ **权重刷新与衰减进主分数**(旧记忆降权、被调用者刷新,带 floor 与保护线、可预演可回滚)⑤ 累积修复:装机即挂(issue #48)、出货面冒烟进门禁(第九腿)、归一层缺省翻关、N230 重放遮蔽 |
|
|
460
461
|
| **[Release v0.6.1](https://github.com/FuRongJun-1999/dsh-memory/releases/tag/v0.6.1)** | 本版变更:外部测试报告逐条核验后的四批修复——① 写面静默失败族(自动 id 加熵与撞车有界重生成 / 声明密级透传且落盘不一致即 fail-closed / 索引分片目录删除后自愈)· ② 合并吞正文与冲突误判(MERGE 与熵 DROP 两分支都保新正文 / 仅空白差异不再误判同一条件分歧 / CCG 哨兵空值不再触发假冲突)· ③ 召回面诚实性(负覆盖条目不再以满分冒充答案且不溢出 k / 一致性飞轮只对真冲突建单 / 显式 k=0 不再被当缺省 / 仅 1 个桶的库不说「分区正常」/ 类型错的参数与裸 null 出口收成结构化错误)· ④ 插件面(全角凭据形态纳入脱敏 / 注入记忆加不可信边界声明 / 子代理委派不写成本人记忆 / 召回与时间线读写两侧会话口径同尺)。九项探针先取证后改码,每处修复配定点变异自证 |
|
|
461
462
|
| **[Release v0.6.0](https://github.com/FuRongJun-1999/dsh-memory/releases/tag/v0.6.0)** | 本版变更:init 一键配置命令(交互三问 → 四端 mcp.json 片段 + 指引)· bin 入口 · README 声明 DSH 0.17.2 已验证 · 检索强化(读缓存 / 智慧书预计算 / 统一归一 / 门控生产路径)· 蜂巢稳定形态与并发防线 · 会话隔离 |
|
package/codebuddy/CODEBUDDY.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
> 本文件由 `scripts/render_discipline.py` 从真源
|
|
4
4
|
> `docs/工作纪律_认知图条目_v1.1.json` 渲染生成,**请勿手改**;改真源后重跑渲染。
|
|
5
|
-
> 真源指纹(SHA256 前16位):
|
|
5
|
+
> 真源指纹(SHA256 前16位):134dd7e69870405d
|
|
6
6
|
|
|
7
7
|
## 概述
|
|
8
8
|
|
|
9
|
-
工作纪律集: 4条方法论(理论先行/全面处理/白箱方法/根因) + 3条执行(验证/双副本/兜底) + 1条思考(中文思考/协作模式分级) + 1条保密(敏感信息隔离) + 3条图像线(选源护栏/历史查询优先/算法权威唯一) + 1条协作(访谈澄清:先问清再动手) + 1条合规(内容政策合规:公开产物过内容政策+隐私双清单) + 2条执行调度与运行环境(命令执行统一走python/UTF-8: 显式encoding+PYTHONUTF8=1, 规避GBK, 全仓文本与路径一律UTF-8(无BOM, 中文可进路径), 不做控制台兼容(取用走python);
|
|
9
|
+
工作纪律集: 4条方法论(理论先行/全面处理/白箱方法/根因) + 3条执行(验证/双副本/兜底) + 1条思考(中文思考/协作模式分级) + 1条保密(敏感信息隔离) + 3条图像线(选源护栏/历史查询优先/算法权威唯一) + 1条协作(访谈澄清:先问清再动手) + 1条合规(内容政策合规:公开产物过内容政策+隐私双清单) + 2条执行调度与运行环境(命令执行统一走python/UTF-8: 显式encoding+PYTHONUTF8=1, 规避GBK, 全仓文本与路径一律UTF-8(无BOM, 中文可进路径), 不做控制台兼容(取用走python); 任务派发统一走蜂巢(zcode端的工作流工具为等效蜂巢工具——使用者2026-10-04裁定; 蜂巢功能完全完善之前可优先使用zcode工作流): 任何执行性任务经蜂巢spawn/submit执行并留痕, agent本体只做编排, L1出口=产物落点为只读判定(不改仓库/外部状态)可直跑须留痕) + 1条记忆闭环(任务收尾归档:每次任务执行完只将核心修改(内容/原因/位置/验证结论)按CCG六要素(功能名/生效条件/子功能/执行/验证方式/不适用条件)成文存入灵枢记忆, 不写中间过程; text 类缺要素即被写入闸门拒并当场返回缺失清单) + 1条信息获取(工作区索引优先: 查工作区文件先读 WORKSPACE_INDEX.md(仓根, 由 scripts/workspace_index.py 从 git 追踪面管线生成), 尚无则先跑 --write 生成再读, 不以重复全盘浏览代替; 索引是会陈化的手工件, 故须管线生成+机械守卫, 否则退化为误导源)
|
|
10
10
|
|
|
11
11
|
## 执行公约
|
|
12
12
|
|
|
@@ -135,9 +135,9 @@
|
|
|
135
135
|
### 17. 任务派发统一走蜂巢
|
|
136
136
|
|
|
137
137
|
- **触发**:执行性任务|多智能体协作|批量/并行工作|探索盘点|运行测试/脚本
|
|
138
|
-
- **动作**:任务→按产物落点判据定层级(不改仓库/外部状态的只读判定=L1直跑并按任务留痕一条(复核者=使用者+GitHub使用者); 改状态=必派发, 不得自我豁免)→拆spec(LLM委托型=默认exec.py: model+user_prompt+context_files; 确定性执行=自定义worker, 契约「读spec.json写result.json」)→hive_spawn/CLI submit→poll→汇总; 兜底情形须声明「蜂巢不可用, 兜底降级」
|
|
138
|
+
- **动作**:任务→按产物落点判据定层级(不改仓库/外部状态的只读判定=L1直跑并按任务留痕一条(复核者=使用者+GitHub使用者); 改状态=必派发, 不得自我豁免)→拆spec(LLM委托型=默认exec.py: model+user_prompt+context_files; 确定性执行=自定义worker, 契约「读spec.json写result.json」)→hive_spawn/CLI submit(zcode端: 工作流工具为等效蜂巢工具——使用者2026-10-04裁定; 蜂巢功能完全完善之前, zcode端可优先使用zcode工作流处理; 留痕以工作流脚本与运行记录为凭)→poll→汇总; 兜底情形须声明「蜂巢不可用, 兜底降级」
|
|
139
139
|
- **不适用**:纯会话内对话/问答(无执行动作)|蜂巢不可用且任务必须立即执行(兜底须声明)|L1只读判定(产物落点=不改仓库/不改外部状态的本地只读命令, 秒级·可逆·高频, 如跑门禁/测试/回归/lint看结论; 判据是产物落点而非「任务简单」的主观感受, 直跑须输出一行「L1 直跑:<命令> — 风险/频次/可逆性」留痕, 留痕粒度按任务(同一任务的多条只读命令合并为一条留痕, 不逐命令逐行); 层级归属的复核者=使用者+GitHub使用者)|情感交互|闲聊
|
|
140
|
-
- **声明**:按工作纪律第17条: 任务派发统一走蜂巢——任何执行性任务经蜂巢 spawn/submit 执行并留痕(spec/status/result), agent本体只做编排; 宿主自带subagent/team
|
|
140
|
+
- **声明**:按工作纪律第17条: 任务派发统一走蜂巢——任何执行性任务经蜂巢 spawn/submit 执行并留痕(spec/status/result), agent本体只做编排; 宿主自带subagent/team不是等价通道(zcode端的工作流工具为等效蜂巢工具——使用者2026-10-04裁定; 蜂巢功能完全完善之前, zcode端可优先使用zcode工作流处理), 兜底须声明。L1只读判定(产物落点=不改仓库/外部状态)可直跑, 须输出「L1 直跑:<命令> — 风险/频次/可逆性」留痕。
|
|
141
141
|
|
|
142
142
|
### 18. 工作区索引优先
|
|
143
143
|
|
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
| 14 | 按工作纪律第14条: 内容政策合规——对外公开产物必须过「内容政策+隐私」双清单,过滤在生成阶段做;已发布发现违规要删条+重写git历史+通知平台清缓存。 |
|
|
167
167
|
| 15 | 按工作纪律第15条: 命令执行统一走python——argv列表+显式UTF-8+PYTHONUTF8=1, 不经Windows shell, 规避GBK解码异常。 另: 全仓文本与路径/文件名一律UTF-8(中文可进路径), 不做控制台兼容(取用走python, 控制台乱码属显示层)。 |
|
|
168
168
|
| 16 | 按工作纪律第16条: 任务收尾归档——每次任务执行完只提炼核心修改(内容/原因/位置/验证结论)并按 CCG 六要素(功能名/生效条件/子功能/执行/验证方式/不适用条件)成文存入灵枢记忆(text 类六要素缺失会被写入闸门当场拒绝并返回缺失清单——补齐后重写即可, 不必等读回确认才发现), 写入后发起一次读取查询确认写入成功且可检索, 禁写中间过程/试错/调试等无效信息, 与第2条形成「查记忆→执行→写记忆→读回确认」闭环。 |
|
|
169
|
-
| 17 | 按工作纪律第17条: 任务派发统一走蜂巢——任何执行性任务经蜂巢 spawn/submit 执行并留痕(spec/status/result), agent本体只做编排; 宿主自带subagent/team
|
|
169
|
+
| 17 | 按工作纪律第17条: 任务派发统一走蜂巢——任何执行性任务经蜂巢 spawn/submit 执行并留痕(spec/status/result), agent本体只做编排; 宿主自带subagent/team不是等价通道(zcode端的工作流工具为等效蜂巢工具——使用者2026-10-04裁定; 蜂巢功能完全完善之前, zcode端可优先使用zcode工作流处理), 兜底须声明。L1只读判定(产物落点=不改仓库/外部状态)可直跑, 须输出「L1 直跑:<命令> — 风险/频次/可逆性」留痕。 |
|
|
170
170
|
| 18 | 按工作纪律第18条: 工作区索引优先——查工作区文件先读 WORKSPACE_INDEX.md(仓根, 管线生成); 无则先跑 scripts/workspace_index.py --write 生成再读, 不以重复全盘浏览代替。 |
|
|
171
171
|
|
|
172
172
|
## 记忆接口速查(`cg` / `stg`)
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
### 第 3 层(`9a1150dd`)
|
|
29
29
|
|
|
30
|
-
- 在役实测:timeline **44.84ms → 0.86ms(52.07
|
|
30
|
+
- 在役实测:timeline **44.84ms → 0.86ms(52.07×)**——**规模前提:7607 条在役库**;加速比随库规模与条件收窄度增长:小库/无条件收窄(命中集=全库)时索引收益低(外部复核在 600 条合成库上实测约 1.4×,且仅条件收窄命中集时有收益);两臂(flag 开/关)逐位等价。
|
|
31
31
|
- 守卫 51 断言+17 处定点变异;独立复核 ACCEPT;python 310/310;容器 43/0 与 96/0/3。
|
|
32
32
|
- 容器 2 fail 定因=issue52 守卫 ANCHOR-MISS 按设计工作(fail-closed)——五组锚同步+容器清单补登后全绿。
|
|
33
33
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# 发布 10 · 0.7.2:读面损坏 UTF-8 家族收口(外部贡献 PR #54 + 同形态残余加固 + 随版工程面)
|
|
2
|
+
|
|
3
|
+
> 发版线:v0.7.1(d257b893)→ v0.7.2
|
|
4
|
+
> 发布三关:`npm run gate`(九条腿)· Linux 容器双栈(rust:bookworm / node:22-bookworm)· E 盘空文件夹沙箱模拟新用户安装
|
|
5
|
+
> 起源:外部贡献 PR #54(贡献者 heimixieb)——读取面遇损坏非 UTF-8 节点崩溃;同形态残余站点扫描与加固(census / restore / read_preimage / locate / health)及随版工程面一并收口
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 一、起因与落地
|
|
10
|
+
|
|
11
|
+
**共同主线:读面损坏 UTF-8 家族**——单个损坏非 UTF-8 的节点文件即可让读取面裸抛 `UnicodeDecodeError`;本版把「读取面不得因坏字节崩溃」的口径从外部贡献的读取面扩展到全部同形态残余站点(跳过 / 结构化失败 / 计数可观测),并随版收口工程面。
|
|
12
|
+
|
|
13
|
+
1. **外部贡献 PR #54(贡献者 heimixieb)**:提交 `30e0580b`(健康检查跳过损坏 UTF-8 节点)+ `ccef3bc0`(读取面跳过)——独立复核为双 worktree 红绿对照:改动前树上 `cg.get` 对坏字节裸抛 `UnicodeDecodeError`(复现红),PR 侧转绿;其 4 条全量红经改动前/主线同环境对照归因 PR 中性(新检出缺 rust release 二进制 fail-closed)→ 合并 `ed15a10e` 并回复(评论 5978111114,回复中含残余站点披露)。
|
|
14
|
+
2. **同形态残余加固(`c8cc4d4e`)**:`census.load` 与 `mreview.locate.load_node` 坏字节随 `OSError` 并入跳过(`md_cg/census.py:55` / `md_cg/mreview/locate.py:315`);`mdcos.restore` 坏 trash 返回 `{ok:False, error:'corrupt'}` 且源原样保留(不删不移,`md_cg/mdcos.py:3023`);`rollback.read_preimage` 坏前像抛 `RollbackError('preimage_unreadable')` fail-closed(不裸抛,`md_cg/rollback.py:178-180`);`health()` 新增 `skipped_unreadable` 显式计数(跳过与不存在在统计上不再同形;代校验键集同步 20 键,`md_cg/mdcg.py:4791-4821`);新守卫 `md_cg/test_corrupt_utf8_read_surfaces.py`(五断言面 29 断言 + `--mutate` 五处定点变异红项 2/3/3/8/1 逐处命中、无跨面误伤)。
|
|
15
|
+
3. **随版工程面**:issue #53 三缺陷(密文假域标签 / 桶计数虚高 / 假 hash_drift,`377b84c8`);CI 收口(PR #47 先构建 Rust/Hive 二进制再跑 Python 全量、套件并行改串行防共享临时目录竞态、charset 表与纪律指纹跨平台同判);外部 v0.7.1 评测报告核验处置(`5b021410`:charset 跨版本三态、性能断言并行豁免、52× 规模前提补记)。
|
|
16
|
+
|
|
17
|
+
## 二、逐批读数
|
|
18
|
+
|
|
19
|
+
### PR #54 面(外部贡献复核)
|
|
20
|
+
|
|
21
|
+
- 双 worktree 红绿对照:改动前树上 `cg.get` 对坏字节裸抛 `UnicodeDecodeError`(复现红);PR 侧转绿。
|
|
22
|
+
- 全量红归因:其 4 条全量红经改动前/主线同环境对照归因 PR 中性——新检出缺 rust release 二进制 fail-closed。
|
|
23
|
+
- 合并与回复:`ed15a10e` 合并;回复评论 5978111114(回复中含残余站点披露)。
|
|
24
|
+
|
|
25
|
+
### 同形态残余加固(`c8cc4d4e`)
|
|
26
|
+
|
|
27
|
+
- 守卫 `md_cg/test_corrupt_utf8_read_surfaces.py`:五断言面 29 断言(census / restore / preimage / locate / health 五面,隔离临时库真跑;不碰工作区与在役库)。
|
|
28
|
+
- `--mutate` 五处定点变异:红项 2/3/3/8/1 逐处命中、无跨面误伤(逐处锚点自检 PASS,锚点漂移即 fail-closed)。
|
|
29
|
+
- 代校验键集 20 键对拍:18 既有 + `bucket_scope`(issue #53-B 扩键)+ `skipped_unreadable`(PR #54 扩键)一字未动。
|
|
30
|
+
|
|
31
|
+
### 发布三关与环境读数(发版树)
|
|
32
|
+
|
|
33
|
+
- 发布三关之一 · 门禁九腿:VERDICT=PASS(rc=0)。
|
|
34
|
+
- 发布三关之二 · Linux 容器栈一(rust:bookworm,`linux_verify.sh full`,含补登后的两条坏 UTF-8 守卫):45 pass / 0 fail(rc=0;`test_health_corrupt_utf8` 与 `test_corrupt_utf8_read_surfaces` 补登入列且均 PASS)。
|
|
35
|
+
- 发布三关之二 · Linux 容器栈二(node:22-bookworm:npm install + npm run build + node --import tsx --test test/*.test.ts):96 pass / 0 fail / 3 skipped(99 项,rc=0)。
|
|
36
|
+
- 发布三关之三 · E 盘空文件夹沙箱模拟新用户安装(保留模式 · pack → install → 真握手逐行 JSON-RPC):VERDICT=PASS(tarball 1545 条目;真装 rc=0 用时 29.2s;真握手 rc=0 用时 1.6s;serverInfo 0.7.2;tools=[cg,stg];沙箱保留供人工复核)。
|
|
37
|
+
- 本地 python 全量(发版树):315/315 通过,5 跳过(依赖缺失/平台不符)。
|
|
38
|
+
- 本地 TS 腿(`npm test`:build + node --import tsx --test):98 pass / 0 fail / 1 skipped(99 项,rc=0)。
|
|
39
|
+
|
|
40
|
+
### 发版提交面(v0.7.1 → 发版)
|
|
41
|
+
|
|
42
|
+
`c8cc4d4e` 读面损坏 UTF-8 残余加固(PR #54 跟进)/`ed15a10e` Merge PR #54 /`bfa1bdea` 纪律 17 条修订 /`377b84c8` issue #53 三缺陷 /`ccef3bc0`+`30e0580b` PR #54 内两提交 /`5b021410` 外部 v0.7.1 报告核验处置 /`49f32a1e` CI 残红清零 /`06dd33ed` 与 `20464231` CI 收口 /`5aa591b6`+`0e2024ea` PR #47。
|
|
43
|
+
|
|
44
|
+
## 三、必须连着读的披露
|
|
45
|
+
|
|
46
|
+
1. **留池的同形态两站点**:白箱子系统自读面(`md_cg/whitebox_kb/wisdom/md_access.py` 的节点装载与边域枚举两处读盘)为同形态站点,不在本版覆盖——归档不适用条件已声明,留池。
|
|
47
|
+
2. **`skipped_unreadable` 属有意扩键**:与 `bucket_scope` 同构的扩键流程(纯增量、与既有键无冲突)——代校验键集 20 键对拍(18 既有 + `bucket_scope` + `skipped_unreadable`)逐字一致。
|
|
48
|
+
3. **restore 的「生效条件」契约行随行为同步**:`mdcos.restore` 的生效条件行与 `rollback.read_preimage` 的 docstring 随坏字节处理一并更新——行为与契约同一改动面。
|
|
49
|
+
4. **容器清单先绿后登**:本版补登两条坏 UTF-8 守卫进 Linux 容器清单(`scripts/linux_verify.sh`)——单跑与 `--mutate` 自证先绿后登。
|
|
50
|
+
|
|
51
|
+
## 四、已知边界(如实登记)
|
|
52
|
+
|
|
53
|
+
1. 白箱子系统自读面两站点留池(见「三、必须连着读的披露」第 1 条)——本版未覆盖。
|
|
54
|
+
2. `gate` 未带 `--cg-root` 时 cg-nodes 投影校验跳过(`scripts/verify_discipline.py` 缺省读环境变量,两者皆无则跳过)——既有观察,待裁。
|
|
55
|
+
3. 本版不涉在役库读写。
|
|
56
|
+
|
|
57
|
+
## 五、发布面与回验
|
|
58
|
+
|
|
59
|
+
- 版本号 8 处:`package.json` · `package-lock.json`(×2)· `.claude-plugin/marketplace.json` · `claude/lingshu-memory/.claude-plugin/plugin.json` · `codex/lingshu-memory/.codex-plugin/plugin.json` · `skills/plugin.json` · `README.md` 正文版本行与版本表(新增 v0.7.2 行,历史行原样)。
|
|
60
|
+
- Linux 容器清单补登:`scripts/linux_verify.sh` 两守卫入列(`test_health_corrupt_utf8` / `test_corrupt_utf8_read_surfaces`)。
|
|
61
|
+
- `WORKSPACE_INDEX.md` 重生成(管线生成,机械守卫在 CI 同判)。
|
|
62
|
+
- 回验:`python scripts/check_publish_artifact.py` · `python scripts/check_publish_smoke.py`(真装真握手)· 发布后 `python -X utf8 scripts/check_registry_tarball.py` · `npx @furongjun1999/dsh-memory init`(装机终验)。
|
|
63
|
+
|
|
64
|
+
## 六、证据面(守卫星座)
|
|
65
|
+
|
|
66
|
+
| 面 | 载体 |
|
|
67
|
+
|:--|:--|
|
|
68
|
+
| PR #54 面 | `md_cg/test_health_corrupt_utf8.py` |
|
|
69
|
+
| 同形态残余加固面 | `md_cg/test_corrupt_utf8_read_surfaces.py`(五断言面 29 断言 + `--mutate` 五处定点变异 2/3/3/8/1 逐处命中) |
|
|
70
|
+
| CI 面 | `.github/workflows`(python-tests 等;PR #47 后先构建 Rust/Hive 二进制再跑 Python 全量) |
|
|
71
|
+
| 报告 | 本文件 |
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"reject": []
|
|
12
12
|
},
|
|
13
13
|
"_comment": "identity负责端口分流; negative只否决明确域外; 无//注释(标准JSON)",
|
|
14
|
-
"content": "工作纪律集: 4条方法论(理论先行/全面处理/白箱方法/根因) + 3条执行(验证/双副本/兜底) + 1条思考(中文思考/协作模式分级) + 1条保密(敏感信息隔离) + 3条图像线(选源护栏/历史查询优先/算法权威唯一) + 1条协作(访谈澄清:先问清再动手) + 1条合规(内容政策合规:公开产物过内容政策+隐私双清单) + 2条执行调度与运行环境(命令执行统一走python/UTF-8: 显式encoding+PYTHONUTF8=1, 规避GBK, 全仓文本与路径一律UTF-8(无BOM, 中文可进路径), 不做控制台兼容(取用走python);
|
|
14
|
+
"content": "工作纪律集: 4条方法论(理论先行/全面处理/白箱方法/根因) + 3条执行(验证/双副本/兜底) + 1条思考(中文思考/协作模式分级) + 1条保密(敏感信息隔离) + 3条图像线(选源护栏/历史查询优先/算法权威唯一) + 1条协作(访谈澄清:先问清再动手) + 1条合规(内容政策合规:公开产物过内容政策+隐私双清单) + 2条执行调度与运行环境(命令执行统一走python/UTF-8: 显式encoding+PYTHONUTF8=1, 规避GBK, 全仓文本与路径一律UTF-8(无BOM, 中文可进路径), 不做控制台兼容(取用走python); 任务派发统一走蜂巢(zcode端的工作流工具为等效蜂巢工具——使用者2026-10-04裁定; 蜂巢功能完全完善之前可优先使用zcode工作流): 任何执行性任务经蜂巢spawn/submit执行并留痕, agent本体只做编排, L1出口=产物落点为只读判定(不改仓库/外部状态)可直跑须留痕) + 1条记忆闭环(任务收尾归档:每次任务执行完只将核心修改(内容/原因/位置/验证结论)按CCG六要素(功能名/生效条件/子功能/执行/验证方式/不适用条件)成文存入灵枢记忆, 不写中间过程; text 类缺要素即被写入闸门拒并当场返回缺失清单) + 1条信息获取(工作区索引优先: 查工作区文件先读 WORKSPACE_INDEX.md(仓根, 由 scripts/workspace_index.py 从 git 追踪面管线生成), 尚无则先跑 --write 生成再读, 不以重复全盘浏览代替; 索引是会陈化的手工件, 故须管线生成+机械守卫, 否则退化为误导源)",
|
|
15
15
|
"execution": {
|
|
16
16
|
"how": "按identity(端口)+任务类型路由到对应纪律"
|
|
17
17
|
},
|
|
@@ -403,7 +403,7 @@
|
|
|
403
403
|
{
|
|
404
404
|
"id": "discipline_hive_dispatch",
|
|
405
405
|
"semantic": "17.任务派发统一走蜂巢",
|
|
406
|
-
"content": "任何执行性任务(跑脚本/测试、批量处理、探索盘点、文本加工、多智能体协作)一律经蜂巢派发执行: agent本体只做编排(拆spec→submit/spawn→poll收result→汇总), 宿主自带subagent/team/Task
|
|
406
|
+
"content": "任何执行性任务(跑脚本/测试、批量处理、探索盘点、文本加工、多智能体协作)一律经蜂巢派发执行: agent本体只做编排(拆spec→submit/spawn→poll收result→汇总), 宿主自带subagent/team/Task不是等价通道; zcode端的工作流工具为等效蜂巢工具(使用者2026-10-04裁定)——在蜂巢功能完全完善之前, zcode端可优先使用zcode工作流处理. 原理: ①执行留痕(spec/status/result落盘可追溯, 会话结束不蒸发); ②统一调度面(worker池/心跳/超时强杀/kill/崩溃恢复); ③dogfooding(自家引擎高频实战打磨, 打破「宿主顺手→跳过→永不打磨」死循环); ④判定成本归零(无例外通道, 杜绝「以为只是探索」滑移). ⑤[2026-09-17 A+C修订] 新增L1出口: 判据外置为产物落点(非「任务简单」的主观感受)——不改仓库/外部状态的本地只读命令属L1直跑(须留痕), 改仓库/外部状态(写文件/git/推远端)必派发. 原理: 层级归属不可由执行者独占自我声明(智能论PROP-DECISION-LAYER-003分层原则第4条「层级归属可被验证单元复核」), 判据须机械可核、豁免须可见. ⑥[2026-09-17 使用者确认] L1留痕粒度=按任务(一个任务一条, 不逐命令逐行); L1层级归属的复核者=使用者+GitHub使用者(坐实分层原则第4条「复核者」位).",
|
|
407
407
|
"conditions": {
|
|
408
408
|
"apply": [
|
|
409
409
|
"执行性任务",
|
|
@@ -423,10 +423,10 @@
|
|
|
423
423
|
]
|
|
424
424
|
},
|
|
425
425
|
"execution": {
|
|
426
|
-
"how": "任务→按产物落点判据定层级(不改仓库/外部状态的只读判定=L1直跑并按任务留痕一条(复核者=使用者+GitHub使用者); 改状态=必派发, 不得自我豁免)→拆spec(LLM委托型=默认exec.py: model+user_prompt+context_files; 确定性执行=自定义worker, 契约「读spec.json写result.json」)→hive_spawn/CLI submit→poll→汇总; 兜底情形须声明「蜂巢不可用, 兜底降级」"
|
|
426
|
+
"how": "任务→按产物落点判据定层级(不改仓库/外部状态的只读判定=L1直跑并按任务留痕一条(复核者=使用者+GitHub使用者); 改状态=必派发, 不得自我豁免)→拆spec(LLM委托型=默认exec.py: model+user_prompt+context_files; 确定性执行=自定义worker, 契约「读spec.json写result.json」)→hive_spawn/CLI submit(zcode端: 工作流工具为等效蜂巢工具——使用者2026-10-04裁定; 蜂巢功能完全完善之前, zcode端可优先使用zcode工作流处理; 留痕以工作流脚本与运行记录为凭)→poll→汇总; 兜底情形须声明「蜂巢不可用, 兜底降级」"
|
|
427
427
|
},
|
|
428
428
|
"response": {
|
|
429
|
-
"direct": "按工作纪律第17条: 任务派发统一走蜂巢——任何执行性任务经蜂巢 spawn/submit 执行并留痕(spec/status/result), agent本体只做编排; 宿主自带subagent/team
|
|
429
|
+
"direct": "按工作纪律第17条: 任务派发统一走蜂巢——任何执行性任务经蜂巢 spawn/submit 执行并留痕(spec/status/result), agent本体只做编排; 宿主自带subagent/team不是等价通道(zcode端的工作流工具为等效蜂巢工具——使用者2026-10-04裁定; 蜂巢功能完全完善之前, zcode端可优先使用zcode工作流处理), 兜底须声明。L1只读判定(产物落点=不改仓库/外部状态)可直跑, 须输出「L1 直跑:<命令> — 风险/频次/可逆性」留痕。"
|
|
430
430
|
}
|
|
431
431
|
},
|
|
432
432
|
{
|
package/md_cg/bench6_arms.py
CHANGED
|
@@ -172,14 +172,18 @@ class LingshuAdapter(Adapter):
|
|
|
172
172
|
"file_head": head})
|
|
173
173
|
return out
|
|
174
174
|
|
|
175
|
-
#
|
|
175
|
+
# 生效条件:无参数,计数与生产同源(MdCG._count_buckets:按 entry.bucket 现算、只含分桶层条目),再把计数字典交 routing.bucket_health 并返回其结果。
|
|
176
176
|
def bucket_health(self):
|
|
177
|
-
"""桶健康度自检(routing.bucket_health):取证条件路由是否有区分力。
|
|
177
|
+
"""桶健康度自检(routing.bucket_health):取证条件路由是否有区分力。
|
|
178
|
+
|
|
179
|
+
issue #53-B④:此前本地版把非分桶层节点全归入 `"<none>"` 一桶——在只含
|
|
180
|
+
contextual 的 bench 库上恒得「单桶/巨桶」报警,读数与生产口径(分桶
|
|
181
|
+
只覆盖 BUCKETED_LAYERS)不一致。改为委托 `MdCG._count_buckets`
|
|
182
|
+
(同源单点:与快照装载/rebuild 同一口径)。
|
|
183
|
+
"""
|
|
178
184
|
from md_cg import routing
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
b = e.get("bucket") or "<none>"
|
|
182
|
-
counts[b] = counts.get(b, 0) + 1
|
|
185
|
+
from md_cg.mdcg import MdCG
|
|
186
|
+
counts = MdCG._count_buckets(self.cg.index["nodes"])
|
|
183
187
|
return routing.bucket_health(counts)
|
|
184
188
|
|
|
185
189
|
|
package/md_cg/census.py
CHANGED
package/md_cg/mdcg.py
CHANGED
|
@@ -2496,15 +2496,12 @@ class MdCG:
|
|
|
2496
2496
|
os.remove(_old_real)
|
|
2497
2497
|
except OSError:
|
|
2498
2498
|
pass # 删除失败不阻断主写路径(残留双文件退回旧行为,可重建兜底)
|
|
2499
|
-
#
|
|
2500
|
-
#
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
self.index["buckets"][_ob] = _left
|
|
2506
|
-
else:
|
|
2507
|
-
self.index["buckets"].pop(_ob, None)
|
|
2499
|
+
# 旧桶计数递减由 `_set_index_entry` 单点接管(issue #53-B):本处
|
|
2500
|
+
# 原先手写一份「仅桶变化时撤旧」——并有一句注释把「同桶覆写的
|
|
2501
|
+
# _stage 自增」当成既有口径,而实测那正是计数虚高的病灶(覆写一次
|
|
2502
|
+
# counts +1,{'orphan': 1} → 2)。撤旧/加新统一在单点按**归属
|
|
2503
|
+
# 变化**判定;若此处再手写一份,换桶覆写会被撤两次、同桶覆写
|
|
2504
|
+
# 仍虚高,两处口径必然分叉。本处不再动计数。
|
|
2508
2505
|
self._stage(node_id, _strip_empty_gate_fields({
|
|
2509
2506
|
"path": os.path.relpath(path, self.root).replace("\\", "/"),
|
|
2510
2507
|
"layer": layer, "tags": tags, "bucket": bucket,
|
|
@@ -3020,7 +3017,7 @@ class MdCG:
|
|
|
3020
3017
|
if len(self._dirty) >= self.autoflush:
|
|
3021
3018
|
self.flush()
|
|
3022
3019
|
|
|
3023
|
-
# 生效条件:无条件把 entry 写入 index["nodes"][node_id](同键覆写 = dict
|
|
3020
|
+
# 生效条件:无条件把 entry 写入 index["nodes"][node_id](同键覆写 = dict 原地更新,物理位序不变);桶计数只在该 id 的桶归属**变化**时调整(经 _bucket_shift 撤旧加新,同归属零动作);self._stg_index 非 None(第 3 层结构索引已构建)时同钩增量增/改该 id,为 None(未构建/已失效)时**不构建**——首次 stg 访问才构建,写路径零额外成本;返回 None。
|
|
3024
3021
|
def _set_index_entry(self, node_id, entry):
|
|
3025
3022
|
"""索引条目写入的**单点**(桶计数 + stg 结构索引同钩)。
|
|
3026
3023
|
|
|
@@ -3028,31 +3025,50 @@ class MdCG:
|
|
|
3028
3025
|
两处口径必然漂开(本仓既有教训:同一判据写两份 = 迟早各漏一次)。
|
|
3029
3026
|
无效化(rebuild/compact/重载)后 `_stg_index` 为 None ⇒ 这里退化为
|
|
3030
3027
|
无操作,读侧「首次需要时构建」的惰性路径接管。
|
|
3028
|
+
|
|
3029
|
+
issue #53-B:同 id 再装入(add 覆写 / backfill 等 _stage)此前对 entry
|
|
3030
|
+
的桶**无条件 +1**——旧条目的计数从不撤下,覆写一次即虚高 1(实测
|
|
3031
|
+
{'orphan': 1} → 覆写后 {'orphan': 2}),health 分桶读数随之失真。
|
|
3032
|
+
改为「归属变化才动计数」:撤旧、加新,同桶覆写零动作。
|
|
3031
3033
|
"""
|
|
3034
|
+
_old = self.index["nodes"].get(node_id)
|
|
3032
3035
|
self.index["nodes"][node_id] = entry
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
+
_ob = _old.get("bucket") if _old is not None else None
|
|
3037
|
+
_nb = entry.get("bucket")
|
|
3038
|
+
if _ob != _nb:
|
|
3039
|
+
self._bucket_shift(_ob, -1)
|
|
3040
|
+
self._bucket_shift(_nb, +1)
|
|
3036
3041
|
ix = getattr(self, "_stg_index", None)
|
|
3037
3042
|
if ix is not None:
|
|
3038
3043
|
ix.add(node_id, entry)
|
|
3039
3044
|
|
|
3040
|
-
# 生效条件:无条件 pop index["nodes"][node_id](不存在则无操作);被 pop
|
|
3045
|
+
# 生效条件:无条件 pop index["nodes"][node_id](不存在则无操作);被 pop 的条目经 _bucket_shift 撤其桶一票;self._stg_index 非 None 时同钩摘除该 id(未构建即不动);返回被摘除的条目(不存在返回 None)。
|
|
3041
3046
|
def _remove_index_entry(self, node_id):
|
|
3042
3047
|
"""索引条目摘除的**单点**(与 `_set_index_entry` 对称:桶计数 + 结构索引同钩)。"""
|
|
3043
3048
|
e = self.index["nodes"].pop(node_id, None)
|
|
3044
|
-
if e
|
|
3045
|
-
|
|
3046
|
-
left = self.index["buckets"].get(b, 0) - 1
|
|
3047
|
-
if left > 0:
|
|
3048
|
-
self.index["buckets"][b] = left
|
|
3049
|
-
else:
|
|
3050
|
-
self.index["buckets"].pop(b, None)
|
|
3049
|
+
if e:
|
|
3050
|
+
self._bucket_shift(e.get("bucket"), -1)
|
|
3051
3051
|
ix = getattr(self, "_stg_index", None)
|
|
3052
3052
|
if ix is not None:
|
|
3053
3053
|
ix.remove(node_id)
|
|
3054
3054
|
return e
|
|
3055
3055
|
|
|
3056
|
+
# 生效条件:bucket 为假值时直接返回;否则 index["buckets"][bucket] 计数 +delta,结果 >0 时写回、<=0 时删除该键;无返回值。
|
|
3057
|
+
def _bucket_shift(self, bucket, delta):
|
|
3058
|
+
"""桶计数 ±1 的**唯一实现**(归零删键)——issue #53-B 的单点收口。
|
|
3059
|
+
|
|
3060
|
+
此前 `_set_index_entry` 只加不减、`_remove_index_entry` 与 `add()` 内
|
|
3061
|
+
各手写一份递减——同一判据三份实现,旧条目归属变化时三处口径必然分叉
|
|
3062
|
+
(本仓既有教训:「同一判据写两份 = 迟早各漏一次」)。
|
|
3063
|
+
"""
|
|
3064
|
+
if not bucket:
|
|
3065
|
+
return
|
|
3066
|
+
left = self.index["buckets"].get(bucket, 0) + delta
|
|
3067
|
+
if left > 0:
|
|
3068
|
+
self.index["buckets"][bucket] = left
|
|
3069
|
+
else:
|
|
3070
|
+
self.index["buckets"].pop(bucket, None)
|
|
3071
|
+
|
|
3056
3072
|
# 生效条件:恒把 self._stg_index 置 None(丢弃已构建的第 3 层结构索引)——凡**整体替换 self.index** 的路径(重载/compact/rebuild)调它:表按身份判代际(`StgIndex.index_obj is self.index`),显式失效让下次 stg 访问重新按新快照构建(惰性失效重建,设计稿 §3.2 二选一之选);恒返回 None、不抛。
|
|
3057
3073
|
def _invalidate_stg_index(self):
|
|
3058
3074
|
"""第 3 层结构索引的失效单点(整体换索引的三处编排点都调它)。"""
|
|
@@ -3099,6 +3115,17 @@ class MdCG:
|
|
|
3099
3115
|
node_id, fm.get("sensitivity"), declared_sensitivity, sealed,
|
|
3100
3116
|
has_sealer=(type(self)._seal_content is not MdCG._seal_content))
|
|
3101
3117
|
atomic_write(path, nodefile.dumps(fm, sealed), durable=durable)
|
|
3118
|
+
# issue #53-C:写盘口同步索引内容指纹——「所有写盘点都应走这里」的
|
|
3119
|
+
# 同一收口面延伸。元数据更新族写点(update_tags / append_edge /
|
|
3120
|
+
# append_subgraph_node / set_edge_condition / backfill_* …)走
|
|
3121
|
+
# 「get() 解密 → 本方法重封」时密文每次变化(AEAD 随机 nonce),
|
|
3122
|
+
# 而它们只同步自己关心的索引键——本键若不在此收口,索引 hash 会停在
|
|
3123
|
+
# **旧密文**上 ⇒ 启动对账每次报 hash_drift 假阳性(实测可经 compact
|
|
3124
|
+
# 快照与进程重开持久存活,直到某次全量重建;报告 issue #53-C)。
|
|
3125
|
+
# 新节点首次写入时条目尚未装入(_stage 紧随其后,值同源)→ 取不到即跳过。
|
|
3126
|
+
_entry = self.index["nodes"].get(node_id)
|
|
3127
|
+
if _entry is not None:
|
|
3128
|
+
_entry["content_hash"] = nodefile.content_hash(sealed)
|
|
3102
3129
|
return sealed
|
|
3103
3130
|
|
|
3104
3131
|
# ---------- S1 前置元数据:域标签回填 ----------
|
|
@@ -3113,7 +3140,8 @@ class MdCG:
|
|
|
3113
3140
|
走 `get()`(解密)→ `_write_node()`(重新封装),保证加密库不会双重封装。
|
|
3114
3141
|
"""
|
|
3115
3142
|
st = {"seen": 0, "already": 0, "written": 0, "no_signal": 0,
|
|
3116
|
-
"unreadable": 0, "
|
|
3143
|
+
"unreadable": 0, "ciphertext": 0, "index_synced": 0,
|
|
3144
|
+
"dry_run": bool(dry_run)}
|
|
3117
3145
|
# 前置:功能未开启时**不做任何写入**——域标签是 S1 的元数据,默认口径不得被改变:
|
|
3118
3146
|
# 否则「回填写索引」会与「_scan_nodes 默认关剥键」冲突,写/重建两条路口径不一致。
|
|
3119
3147
|
if os.environ.get("MDCG_RETRIEVAL_PIPELINE") != "1":
|
|
@@ -3143,6 +3171,17 @@ class MdCG:
|
|
|
3143
3171
|
if content is None:
|
|
3144
3172
|
st["unreadable"] += 1
|
|
3145
3173
|
continue
|
|
3174
|
+
from . import crypto
|
|
3175
|
+
if crypto.is_encrypted(content):
|
|
3176
|
+
# issue #53-A:密文不可分类——无密钥实例(backfill CLI 以基类
|
|
3177
|
+
# MdCG 打开库)的 get() 把密文**原样**交出,base64 噪声会碰巧
|
|
3178
|
+
# 命中大域词表(实测唯一命中=「经济」:词表唯一拉丁词 GDP 被
|
|
3179
|
+
# 三连子串撞上),假标签再被 already 分支永久固化。此处**单列
|
|
3180
|
+
# 计数**并跳过(不与 no_signal 叠加:各计数是互斥分区);
|
|
3181
|
+
# classify_text 单点也已同源设防(双层:此层给可观测计数,
|
|
3182
|
+
# 单点层保证任何调用方都不会误分类密文)。
|
|
3183
|
+
st["ciphertext"] += 1
|
|
3184
|
+
continue
|
|
3146
3185
|
try:
|
|
3147
3186
|
dom = routing.classify_text(content)
|
|
3148
3187
|
except Exception:
|
|
@@ -3227,6 +3266,8 @@ class MdCG:
|
|
|
3227
3266
|
try:
|
|
3228
3267
|
with open(p, encoding="utf-8") as f:
|
|
3229
3268
|
fm, content = nodefile.loads(f.read())
|
|
3269
|
+
except UnicodeDecodeError:
|
|
3270
|
+
return None
|
|
3230
3271
|
except OSError as exc:
|
|
3231
3272
|
# C-3(FI-M02 / N134):瞬时读失败**不得静默**——记账(真缺不记)。
|
|
3232
3273
|
# 返回值语义不变(None = 不可读),本方法不入任何缓存,本处无负缓存
|
|
@@ -3267,6 +3308,8 @@ class MdCG:
|
|
|
3267
3308
|
try:
|
|
3268
3309
|
with open(p, encoding="utf-8") as f:
|
|
3269
3310
|
return nodefile.loads(f.read()) + (None,)
|
|
3311
|
+
except UnicodeDecodeError:
|
|
3312
|
+
return None, None, None
|
|
3270
3313
|
except OSError as exc:
|
|
3271
3314
|
if self._note_read_oserror(p, exc):
|
|
3272
3315
|
return None, None, fsutil.READ_FAIL_TRANSIENT
|
|
@@ -3679,7 +3722,7 @@ class MdCG:
|
|
|
3679
3722
|
try:
|
|
3680
3723
|
with open(full_path, encoding="utf-8") as f:
|
|
3681
3724
|
fm, content = nodefile.loads(f.read())
|
|
3682
|
-
except OSError:
|
|
3725
|
+
except (OSError, UnicodeDecodeError):
|
|
3683
3726
|
continue
|
|
3684
3727
|
if any(t in content for t in terms):
|
|
3685
3728
|
neg_coverage.append(e)
|
|
@@ -4732,15 +4775,29 @@ class MdCG:
|
|
|
4732
4775
|
h = routing.bucket_health(self.index.get("buckets", {}),
|
|
4733
4776
|
total_nodes=len(self.index["nodes"]))
|
|
4734
4777
|
h["total_nodes"] = len(self.index["nodes"])
|
|
4778
|
+
# issue #53-B③:分桶读数的**覆盖范围自述**——分桶物理上只存在于
|
|
4779
|
+
# BUCKETED_LAYERS(只 knowledge),非分桶层节点不进 buckets 计数。
|
|
4780
|
+
# 此前读数不带范围,报告方把「nodes=4 / total_nodes=505」读成
|
|
4781
|
+
# 「统计建立在个位数样本上」(实为「knowledge 层 4 个带桶条目」)
|
|
4782
|
+
# ——加一行的成本买断这个歧义。
|
|
4783
|
+
h["bucket_scope"] = list(BUCKETED_LAYERS)
|
|
4735
4784
|
# 5 要素完整度(全节点扫一遍,可能慢但只在 health() 调用)
|
|
4736
4785
|
layer_stats = {}
|
|
4737
4786
|
neg_stats = {}
|
|
4787
|
+
# PR #54 的跳过是**静默**的:被跳过的节点不进 layer_stats,读数对
|
|
4788
|
+
# 「少算了几个」无自述(跳过与不存在在统计上同形)。加显式计数:
|
|
4789
|
+
# 纯增量、与既有键无冲突,与 bucket_scope 同构的扩键流程。
|
|
4790
|
+
# skipped_unreadable=0 ⇔ 索引内每个节点本进程都读盘成功。
|
|
4791
|
+
skipped_unreadable = 0
|
|
4738
4792
|
for e in list(self.index["nodes"].values()):
|
|
4739
4793
|
full_path = self._node_disk_path(e)
|
|
4740
4794
|
try:
|
|
4741
4795
|
with open(full_path, encoding="utf-8") as f:
|
|
4742
4796
|
fm, content = nodefile.loads(f.read())
|
|
4743
|
-
except OSError:
|
|
4797
|
+
except (OSError, UnicodeDecodeError):
|
|
4798
|
+
# 与索引重建同口径:损坏的非 UTF-8 节点不应让健康端点
|
|
4799
|
+
# 整体崩溃;跳过该节点,保留其余盘面读数。
|
|
4800
|
+
skipped_unreadable += 1
|
|
4744
4801
|
continue
|
|
4745
4802
|
cpl = nodefile.ccg_completeness(content)
|
|
4746
4803
|
layer = e["layer"]
|
|
@@ -4761,4 +4818,5 @@ class MdCG:
|
|
|
4761
4818
|
neg_stats[layer] += 1
|
|
4762
4819
|
h["ccg_by_layer"] = layer_stats
|
|
4763
4820
|
h["neg_memory_counts"] = neg_stats
|
|
4764
|
-
|
|
4821
|
+
h["skipped_unreadable"] = skipped_unreadable
|
|
4822
|
+
return h
|
package/md_cg/mdcos.py
CHANGED
|
@@ -3005,7 +3005,7 @@ class MdCGOS(MdCG):
|
|
|
3005
3005
|
def is_tombstoned(self, node_id: str):
|
|
3006
3006
|
return any(r.get("id") == node_id for r in read_jsonl(self.deletions_log))
|
|
3007
3007
|
|
|
3008
|
-
# 生效条件:当 node_id 传入时,若 deletions_log 中存在该 id 且 force=False 返回 tombstoned 拒绝;否则检查 trash_dir/{node_id}.md,os.path.exists 为 False 返回 not_in_trash
|
|
3008
|
+
# 生效条件:当 node_id 传入时,若 deletions_log 中存在该 id 且 force=False 返回 tombstoned 拒绝;否则检查 trash_dir/{node_id}.md,os.path.exists 为 False 返回 not_in_trash;文件含坏字节(非 UTF-8)无法解析时返回 error=corrupt 且 trash 源原样保留(不删不移);可打开则读取并以 add(override=True) + trash frontmatter 元数据全量透传(显式形参逐项传,其余键经 **extra 回写;lifecycle_state 仅在 active→该态合法迁移时透传)恢复、移除 trash 源、audit,返回 ok True/id/forced=bool(force);
|
|
3009
3009
|
def restore(self, node_id: str, force: bool = False):
|
|
3010
3010
|
"""恢复:若在删除清单中且未 force → 拒绝(恢复时删除检查)。"""
|
|
3011
3011
|
tomb = [r for r in read_jsonl(self.deletions_log) if r.get("id") == node_id]
|
|
@@ -3015,8 +3015,14 @@ class MdCGOS(MdCG):
|
|
|
3015
3015
|
src = os.path.join(self.trash_dir, f"{node_id}.md")
|
|
3016
3016
|
if not os.path.exists(src):
|
|
3017
3017
|
return {"ok": False, "error": "not_in_trash"}
|
|
3018
|
-
|
|
3019
|
-
|
|
3018
|
+
try:
|
|
3019
|
+
with open(src, encoding="utf-8") as f:
|
|
3020
|
+
fm, content = nodefile.loads(f.read())
|
|
3021
|
+
except UnicodeDecodeError:
|
|
3022
|
+
# trash 源损坏:返回结构化失败,源文件原样保留(不删不移)
|
|
3023
|
+
return {"ok": False, "error": "corrupt",
|
|
3024
|
+
"reason": "trash 文件非 UTF-8(损坏),恢复中止;"
|
|
3025
|
+
"源文件原样保留"}
|
|
3020
3026
|
layer = fm.get("layer", "knowledge")
|
|
3021
3027
|
# 恢复 = 原样放回:add 是**全量重建 fm**,只传个位数字段会把 edges/
|
|
3022
3028
|
# depends_on/验证态/生命周期/created_at/语义摘要/证据计数等元数据永久
|
package/md_cg/mreview/locate.py
CHANGED
|
@@ -312,7 +312,7 @@ def load_node(node_id, root, *, index=None) -> dict:
|
|
|
312
312
|
try:
|
|
313
313
|
with open(fp, encoding="utf-8") as f:
|
|
314
314
|
text = f.read()
|
|
315
|
-
except OSError:
|
|
315
|
+
except (OSError, UnicodeDecodeError):
|
|
316
316
|
return {"node_id": node_id, "meta": dict(meta), "fm": {}, "content": None,
|
|
317
317
|
"text": None, "path": fp}
|
|
318
318
|
fm, content = NF.loads(text)
|
package/md_cg/rollback.py
CHANGED
|
@@ -155,7 +155,7 @@ def collect_impact(cg, node_id):
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
|
|
158
|
-
# 生效条件:rel 指向的文件不存在时抛 RollbackError(code="preimage_missing");存在则读取并以 cg._open_content(node_id, fm, raw) 对称解封,返回 (fm, content)——content 为 None(无密钥/身份不符)时抛 RollbackError(code="preimage_unreadable")
|
|
158
|
+
# 生效条件:rel 指向的文件不存在时抛 RollbackError(code="preimage_missing");存在则读取并以 cg._open_content(node_id, fm, raw) 对称解封,返回 (fm, content)——content 为 None(无密钥/身份不符)时抛 RollbackError(code="preimage_unreadable");文件含坏字节(非 UTF-8)无法解析时同样抛 RollbackError(code="preimage_unreadable")(fail-closed,不裸抛 UnicodeDecodeError);
|
|
159
159
|
def read_preimage(cg, node_id, rel):
|
|
160
160
|
"""读执行时点前像文件(**与 snapshot_preimage 对称**:写走 _write_node 封装、
|
|
161
161
|
读走 _open_content 解封)——返回 (fm, content)。
|
|
@@ -163,14 +163,23 @@ def read_preimage(cg, node_id, rel):
|
|
|
163
163
|
注意:快照文件以节点文件同款 nodefile 序列化落盘;加密库中其内容为密封
|
|
164
164
|
形态,必须经解封钩子读,直接 nodefile.loads 会把密文当明文再次封装
|
|
165
165
|
(双重封装)。非加密库两钩子恒等(探针实测逐字节一致)。
|
|
166
|
+
|
|
167
|
+
文件含坏字节(非 UTF-8)无法解析时抛 RollbackError(code="preimage_unreadable")
|
|
168
|
+
——fail-closed,不裸抛 UnicodeDecodeError。
|
|
166
169
|
"""
|
|
167
170
|
p = os.path.join(cg.root, str(rel).replace("/", os.sep))
|
|
168
171
|
if not os.path.isfile(p):
|
|
169
172
|
raise RollbackError("preimage_missing",
|
|
170
173
|
"前像文件不存在:%s——变更单可能未执行(无前像)"
|
|
171
174
|
"或快照被移走;fail-closed 不猜一个前像。" % rel)
|
|
172
|
-
|
|
173
|
-
|
|
175
|
+
try:
|
|
176
|
+
with open(p, encoding="utf-8") as f:
|
|
177
|
+
fm, raw = nodefile.loads(f.read())
|
|
178
|
+
except UnicodeDecodeError as exc:
|
|
179
|
+
# 坏字节/非 UTF-8:fail-closed——绝不让 UnicodeDecodeError 裸抛逃逸
|
|
180
|
+
raise RollbackError("preimage_unreadable",
|
|
181
|
+
"前像文件坏字节 / 非 UTF-8 无法解析:%s(%s)——"
|
|
182
|
+
"fail-closed 不回滚。" % (rel, exc))
|
|
174
183
|
content = cg._open_content(node_id, fm, raw)
|
|
175
184
|
if content is None:
|
|
176
185
|
raise RollbackError("preimage_unreadable",
|
package/md_cg/routing.py
CHANGED
|
@@ -314,7 +314,7 @@ def bucket_health(counts: dict, total_nodes: int = None) -> dict:
|
|
|
314
314
|
# 巨桶/期望扫描两条在 nb==1 时只是同一事实的两种写法,合并为一条如实读数。
|
|
315
315
|
# expected_scan 的**数值**仍在返回 dict 里(下游读的是数:test_p0 :92、
|
|
316
316
|
# bench_axis_domain :159),只有文案不再把「未分区」说成「巨桶退化」。
|
|
317
|
-
problems.append(f"
|
|
317
|
+
problems.append(f"单桶:仅 1 个分桶({n} 个已分桶节点同桶),"
|
|
318
318
|
"条件路由本就不可用(从未分区,非分区退化)")
|
|
319
319
|
elif expected_scan > 0.30:
|
|
320
320
|
problems.append(f"路由无效:期望扫描 {expected_scan:.1%}(接近全量)")
|
|
@@ -417,5 +417,14 @@ def classify_text(text, limit: int = 400):
|
|
|
417
417
|
|
|
418
418
|
返回 None 表示「无有效域信号」——此时**不写** big_domain 字段,
|
|
419
419
|
该节点留在 ORPHAN/兜底池(S1 收敛时必须能被兜底召回,见契约 §3 S1 不变量)。
|
|
420
|
+
|
|
421
|
+
issue #53-A:密文(`<!-- mdcg-enc:v1:…`)恒无域信号——无密钥的读面会把
|
|
422
|
+
密文**原样**交到这里,base64 噪声会碰巧命中词表(实测唯一命中形态=「经济」:
|
|
423
|
+
词表里唯一的拉丁词 GDP 被 base64 三连子串撞上),且假标签会被 backfill 的
|
|
424
|
+
already 幂等分支永久固化。判据收在此**唯一致敏点**:节点侧全部调用方
|
|
425
|
+
(add 写入面、backfill 回填面)自动同覆盖,无需各自设防。
|
|
420
426
|
"""
|
|
427
|
+
from . import crypto # 延迟导入:routing 是底层模块,避免加载序耦合
|
|
428
|
+
if crypto.is_encrypted(text):
|
|
429
|
+
return None
|
|
421
430
|
return big_domain_classify(domain_terms(text, limit))
|