@furongjun1999/dsh-memory 0.6.1 → 0.7.1

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.
Files changed (105) hide show
  1. package/README.md +49 -18
  2. package/docs/README.md +1 -0
  3. package/docs/eval/cons200_/345/206/262/347/252/201/346/243/200/346/265/213/351/200/211/351/235/242_/345/256/236/346/226/275/350/256/260/345/275/225_v1.0.md +223 -0
  4. package/docs/eval/cons200_/345/206/262/347/252/201/346/243/200/346/265/213/351/200/211/351/235/242_/345/256/236/346/226/275/350/256/260/345/275/225_v1.1.md +340 -0
  5. package/docs/eval/issue50_/345/205/203/346/225/260/346/215/256/351/200/217/344/274/240/344/270/216/345/205/234/345/272/225_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +437 -0
  6. package/docs/eval/issue50_/345/215/212/351/207/215/345/244/215/345/276/205/345/256/232/345/244/215/346/240/270_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +488 -0
  7. package/docs/eval/issue50_/345/276/205/345/256/232/345/244/215/346/240/270/345/205/245/351/230/237_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +428 -0
  8. package/docs/eval/issue50_/350/257/273/351/235/242/344/277/235/346/212/244/345/217/252/350/256/244/346/230/276/345/274/217/346/235/245/346/272/220_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +271 -0
  9. package/docs/eval/issue50_/351/207/215/350/246/201/345/272/246/345/220/214/346/272/220/344/270/216/344/277/235/346/212/244/350/257/255/344/271/211_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +306 -0
  10. package/docs/eval/issue51_/344/270/200/351/224/256/345/256/211/350/243/205/345/244/261/350/264/245_/345/275/222/345/261/236/345/210/244/345/256/232_v1.0.md +58 -0
  11. package/docs/eval/issue52_/346/235/241/344/273/266/345/205/210/350/241/214/344/270/216/346/210/252/346/226/255/345/217/257/350/247/202/346/265/213_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +360 -0
  12. package/docs/eval//344/270/211/346/241/243/350/207/252/346/262/273_/346/255/245/351/252/244/342/221/241/346/241/243/344/275/215/345/215/225/344/270/200/345/205/245/345/217/243_/350/220/275/347/240/201/350/256/260/345/275/225_v1.0.md +651 -0
  13. package/docs/eval//344/270/211/346/241/243/350/207/252/346/262/273_/346/255/245/351/252/244/342/221/242/345/217/230/346/233/264/345/215/225/344/270/216/345/233/236/346/273/232/345/216/237/350/257/255_/350/220/275/347/240/201/350/256/260/345/275/225_v1.0.md +439 -0
  14. package/docs/eval//344/270/211/346/241/243/350/207/252/346/262/273_/346/255/245/351/252/244/342/221/243/345/207/206/345/205/245/350/257/273/346/225/260_/350/220/275/347/240/201/350/256/260/345/275/225_v1.0.md +1337 -0
  15. package/docs/eval//344/270/211/346/241/243/350/207/252/346/262/273_/346/255/245/351/252/244/342/221/244/346/224/266/345/256/230/344/270/216/345/205/250/351/223/276/351/252/214/346/224/266_/350/220/275/347/240/201/350/256/260/345/275/225_v1.0.md +1817 -0
  16. package/docs/eval//345/207/272/350/264/247/351/235/242/345/206/222/347/203/237_/350/277/233/350/264/247/351/227/250/347/246/201_v1.0.md +460 -0
  17. package/docs/eval//345/217/221/345/270/20308_/350/207/252/350/277/255/344/273/243/344/270/216/347/235/241/347/234/240_/345/233/276/346/243/200/347/264/242/350/267/257/344/270/216/346/235/203/351/207/215_v1.0.md +97 -0
  18. 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 +66 -0
  19. package/docs/eval//345/275/222/344/270/200/345/261/202/347/274/272/347/234/201/347/277/273/345/205/263_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +458 -0
  20. package/docs/eval//347/254/2543/345/261/202stg/347/273/223/346/236/204/347/264/242/345/274/225_/345/256/236/346/226/275/350/256/260/345/275/225_v1.0.md +250 -0
  21. package/docs/hive//346/243/200/347/264/242/347/256/227/346/263/225/345/217/243/345/276/204/345/257/271/347/205/247_v0.1.md +136 -11
  22. package/docs/hive//346/243/200/347/264/242/350/267/257/345/276/204/344/270/216/350/256/244/347/237/245/347/273/223/346/236/204/345/245/221/347/272/246_v0.1.md +32 -2
  23. package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +88 -0
  24. package/docs/mdcg//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +40 -40
  25. package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +47 -11
  26. package/docs/mdcg//347/235/241/347/234/240/345/221/250/346/234/237_/350/277/220/347/273/264/345/211/215/346/217/220/344/270/216/347/273/264/346/212/244/346/214/207/345/215/227_v1.0.md +183 -0
  27. package/docs/plans/stg/346/235/241/344/273/266/345/214/226/344/270/216/347/273/223/346/236/204/347/264/242/345/274/225_/350/256/276/350/256/241_v0.1.md +124 -0
  28. package/docs/plans//347/235/241/347/234/240/344/270/216/350/207/252/350/277/255/344/273/243_/345/212/237/350/203/275/344/274/230/345/214/226/350/256/276/350/256/241_v0.4.md +547 -0
  29. package/docs/plans//350/256/260/345/277/206/350/207/252/345/244/204/347/220/206/344/270/211/346/241/243/350/207/252/346/262/273_/350/256/276/350/256/241_v0.2.md +207 -0
  30. package/md_cg/admission.py +718 -0
  31. package/md_cg/autonomy_modes.py +642 -0
  32. package/md_cg/bench_e2e_locomo_qa.py +11 -4
  33. package/md_cg/chain.py +47 -0
  34. package/md_cg/consistency.py +133 -8
  35. package/md_cg/forgetting.py +38 -6
  36. package/md_cg/freshness.py +527 -0
  37. package/md_cg/generation.py +409 -0
  38. package/md_cg/hotcache.py +4 -1
  39. package/md_cg/lifecycle.py +30 -2
  40. package/md_cg/mcp_server.py +191 -26
  41. package/md_cg/mdcg.py +594 -39
  42. package/md_cg/mdcos.py +869 -38
  43. package/md_cg/nodefile.py +74 -1
  44. package/md_cg/protect.py +79 -4
  45. package/md_cg/provenance.py +1 -1
  46. package/md_cg/review_cli.py +31 -2
  47. package/md_cg/rollback.py +411 -0
  48. package/md_cg/rollback_cli.py +104 -0
  49. package/md_cg/semantic/canonical.py +22 -0
  50. package/md_cg/semantic/unify.py +73 -22
  51. package/md_cg/semantic/unify_fixture.json +25 -0
  52. package/md_cg/sleep.py +1297 -0
  53. package/md_cg/stg.py +400 -50
  54. package/md_cg/stgidx.py +281 -0
  55. package/md_cg/sustain.py +146 -9
  56. package/md_cg/test_auto_defaults.py +424 -0
  57. package/md_cg/test_autonomy_admission.py +1098 -0
  58. package/md_cg/test_autonomy_modes.py +1972 -0
  59. package/md_cg/test_b1_auto_id_multiproc.py +7 -0
  60. package/md_cg/test_b1b2_write_face.py +7 -0
  61. package/md_cg/test_b3_merge_keeps_content.py +7 -0
  62. package/md_cg/test_boundary_hit.py +410 -0
  63. package/md_cg/test_cons200_scan_selection.py +791 -0
  64. package/md_cg/test_en_pipeline.py +22 -13
  65. package/md_cg/test_generation_guard.py +352 -0
  66. package/md_cg/test_h4_sustain_snapshot.py +14 -4
  67. package/md_cg/test_i50a_half_dup_defer.py +408 -0
  68. package/md_cg/test_i50b_defer_to_review_queue.py +545 -0
  69. package/md_cg/test_i50c_meta_passthrough.py +608 -0
  70. package/md_cg/test_i50d_importance_source.py +531 -0
  71. package/md_cg/test_i50e_readside_protection.py +656 -0
  72. package/md_cg/test_issue39_utf8_stdio.py +9 -1
  73. package/md_cg/test_issue52_scan_condition_first.py +677 -0
  74. package/md_cg/test_linkref.py +7 -0
  75. package/md_cg/test_mode_parity.py +1393 -0
  76. package/md_cg/test_mutation_rollback.py +805 -0
  77. package/md_cg/test_n204_n205_n226_n227_n228_n229_exit_gates.py +1 -1
  78. package/md_cg/test_n212_n213_n224_generation_gates.py +14 -8
  79. package/md_cg/test_n214_n215_n221_n222_write_face_gates.py +7 -0
  80. package/md_cg/test_n230_dirty_replay.py +375 -0
  81. package/md_cg/test_p2_mcp.py +8 -0
  82. package/md_cg/test_p2_six_elements.py +463 -0
  83. package/md_cg/test_p3_legacy_closure.py +433 -0
  84. package/md_cg/test_p4_freshness.py +680 -0
  85. package/md_cg/test_p8_subgraph_chain.py +14 -1
  86. package/md_cg/test_p9_forget_protect.py +7 -0
  87. package/md_cg/test_p9c_dedup_hints.py +7 -0
  88. package/md_cg/test_policy_required_ccg.py +5 -1
  89. package/md_cg/test_protocol.py +7 -0
  90. package/md_cg/test_rank_parity_score_mode.py +6 -0
  91. package/md_cg/test_semantic_canonical.py +5 -3
  92. package/md_cg/test_sleep.py +611 -0
  93. package/md_cg/test_sleep_p1.py +784 -0
  94. package/md_cg/test_stgidx_index_parity.py +1020 -0
  95. package/md_cg/test_time_core_lint.py +968 -0
  96. package/md_cg/test_unify_default_off.py +701 -0
  97. package/md_cg/test_unify_scope.py +182 -0
  98. package/md_cg/test_writelimit.py +36 -9
  99. package/md_cg/test_writepipe.py +5 -2
  100. package/md_cg/weights.py +15 -1
  101. package/md_cg/whitebox_kb/aeis_core/time_core.py +8 -0
  102. package/md_cg/writepipe.py +101 -2
  103. package/package.json +3 -2
  104. package/skills/plugin.json +1 -1
  105. package/utf8_boot.py +237 -0
@@ -0,0 +1,182 @@
1
+ # -*- coding: utf-8 -*-
2
+ """统一归一层作用域收窄守卫(2026-09-30 使用者裁定)
3
+
4
+ 口径:`semantic/unify.py::unify_query` **只对英文内容做翻译归一,中文内容
5
+ 原样不动**——query 按中文段/非中文段切开,中文段逐字保留、绝不送 segment。
6
+ 旧口径「含任一 ASCII 字母即整条归一」把中夹英 query 的中文部分逐字切开
7
+ (「自我接纳」→「自 我 接 纳」),本件钉住收窄后的语义。
8
+
9
+ **开关前提**:归一层缺省**关**(2026-09-30 使用者裁定,唯一开态 = 显式
10
+ `MDCG_UNIFY_QUERY=1`);本件第 1 节钉三态,其后各节一律**显式开**下跑。
11
+ 缺省态的三态与两侧一致性守卫见 `md_cg/test_unify_default_off.py`。
12
+
13
+ 两侧同源:期望值取自 `md_cg/semantic/unify_fixture.json`——Rust 侧
14
+ `rust/src/atoms.rs::tests::unify_mixed_fixture_matches_python` 用 include_str!
15
+ 嵌入**同一份**文件;任一侧漂移即红(两侧逐位相同)。
16
+
17
+ 跑法:python -X utf8 -m md_cg.test_unify_scope
18
+ """
19
+ import json
20
+ import os
21
+ import sys
22
+
23
+ HERE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
24
+ if HERE not in sys.path:
25
+ sys.path.insert(0, HERE)
26
+
27
+ from md_cg.semantic.canonical import is_zh_char # noqa: E402
28
+ from md_cg.semantic.unify import unify_on, unify_query # noqa: E402
29
+
30
+ FIXTURE = os.path.join(HERE, "md_cg", "semantic", "unify_fixture.json")
31
+
32
+ N = 0
33
+ BAD = []
34
+
35
+
36
+ # 生效条件:单参 cond 为真值判定、msg 为说明串;恒累加计数 N,cond 为假时把 msg 记入模块级 BAD(**不中断**)——收集式断言使「定点变异红了几项」可数(首条即停的 assert 风格数不出红项数)。
37
+ def ok(cond, msg):
38
+ global N
39
+ N += 1
40
+ if not cond:
41
+ BAD.append(msg)
42
+
43
+
44
+ # 生效条件:单参 text 为任意字符串,按 canonical.is_zh_char 切出其中的中文连续段,返回保序的段列表(无中文段时为空列表);仅供本守卫做「中文段逐字保留」的结构判据。
45
+ def zh_runs(text):
46
+ """文本里的中文连续段(判据同 unify_query:canonical.is_zh_char)。"""
47
+ out, buf = [], []
48
+ for ch in text:
49
+ if is_zh_char(ch):
50
+ buf.append(ch)
51
+ elif buf:
52
+ out.append("".join(buf))
53
+ buf = []
54
+ if buf:
55
+ out.append("".join(buf))
56
+ return out
57
+
58
+
59
+ # ---- 1 · 开关三态(2026-09-30 使用者裁定:缺省由开翻为关;唯一开态 = 显式 "1")----
60
+ # 本件测的是**作用域收窄**语义,故从本节之后各节必须在**显式开**(=1)下跑:
61
+ # 缺省已关(未设即关),若不显式开,下面 3~8 节全会退化成恒真(对实现零约束力)。
62
+ # 缺省三态守卫另立一件:md_cg/test_unify_default_off.py(两件分工不同,勿互推)。
63
+ ok(unify_on() is False,
64
+ "MDCG_UNIFY_QUERY 未设即关(缺省关,2026-09-30 翻)")
65
+ os.environ["MDCG_UNIFY_QUERY"] = "0"
66
+ try:
67
+ ok(unify_on() is False, "=0 显式关")
68
+ for t in ("I eat beef yesterday", "领养 LGBTQ 群体", "自我接纳",
69
+ " beef 报告 ", "2024 报告"):
70
+ ok(unify_query(t) == t, "开关关:一律原样返回 %r" % t)
71
+ finally:
72
+ os.environ.pop("MDCG_UNIFY_QUERY", None)
73
+ os.environ["MDCG_UNIFY_QUERY"] = "1"
74
+ ok(unify_on() is True, "=1 显式开(英文对照路要用)——本件余下各节的运行前提")
75
+
76
+ # ---- 2 · 空/None/无 ASCII 字母:原样返回(未 strip 的入参)----
77
+ ok(unify_query(None) is None, "None 原样")
78
+ ok(unify_query("") == "", "空串原样")
79
+ ok(unify_query(" 自我接纳 ") == " 自我接纳 ",
80
+ "纯中文(无 ASCII 字母)原样返回入参本身(不 strip)")
81
+ ok(unify_query("2024 报告") == "2024 报告", "纯中文+数字(无字母)原样")
82
+ ok(unify_query(",。") == ",。", "纯标点(无字母)原样")
83
+
84
+ # ---- 3 · 纯英文仍翻译(收窄不得关掉英文链路)----
85
+ ok(unify_query("I eat beef yesterday") == "我 吃 牛肉 昨天",
86
+ "纯英文仍归一:%r" % unify_query("I eat beef yesterday"))
87
+ ok(unify_query("wrote") == "写", "纯英文时态还原仍生效")
88
+
89
+ # ---- 4 · 纯中文原样(旧口径会被逐字切开)----
90
+ for t, why in (("自我接纳", "旧口径「自 我 接 纳」"),
91
+ ("慈善跑", "旧口径「慈 善 跑」"),
92
+ ("蜂群调度 依赖门禁", "旧口径逐字切开")):
93
+ ok(unify_query(t) == t, "纯中文原样(%s):%r" % (why, t))
94
+
95
+ # ---- 5 · fixture 逐位对拍(两侧同源:Rust 侧嵌同一份文件)----
96
+ with open(FIXTURE, encoding="utf-8") as f:
97
+ FX = json.load(f)["cases"]
98
+ ok(len(FX) >= 12, "fixture 用例数 >= 12:%d" % len(FX))
99
+ for c in FX:
100
+ got = unify_query(c["in"])
101
+ ok(got == c["out"],
102
+ "fixture 逐位不符 [%s] in=%r got=%r want=%r"
103
+ % (c["name"], c["in"], got, c["out"]))
104
+
105
+ # ---- 5b · 单侧钉:任务原型例(Python 侧形态)----
106
+ # 这两例**不入共享 fixture**:非中文段是未登录的**大写**词(LGBTQ),两侧对
107
+ # 「未命中词表的大写词」存在**既有**非对称(Python 保留原大小写 / Rust 经
108
+ # normalize_en 一律 lower 化——用 rust 单测实测 `领养 lgbtq 群体` 取证,
109
+ # 2026-09-30),故两侧钉不出同一期望值。此非本次收窄引入,也非本次范围;
110
+ # 但它是端到端(Python 侧)实际生效的形态,故在此单侧钉死。
111
+ for t in ("领养 LGBTQ 群体 支持 包容", "领养 机构 LGBTQ 群体 支持 包容"):
112
+ ok(unify_query(t) == t, "中夹英原型例:中文段逐字保留、英文片段原样:%r" % t)
113
+
114
+ # ---- 6 · 结构判据(对**实现产物**断言):混合 query 的中文段逐字保留 ----
115
+ # 2026-09-30 清理批次(甲2)修假守卫:本节此前比的是 fixture 的**期望值**
116
+ # (`run in c["out"]`——拿期望比期望),对实现零约束力:做「中文段也送
117
+ # segment」变异时本节 0 项红。现改为直接调 `unify_query` 并对**产物**断言:
118
+ # 中文段必须在产物里逐字出现,且其逐字切开形(`" ".join(run)`)不得出现。
119
+ # 单字中文段(如「年」)的切开形与其本身同形,故切开判据只在 len(run) > 1
120
+ # 时施加(否则是恒假的伪红)。
121
+ for c in FX:
122
+ got = unify_query(c["in"])
123
+ for run in zh_runs(c["in"]):
124
+ ok(run in got,
125
+ "中文段未逐字保留(被改写)[%s] in=%r 段=%r got=%r"
126
+ % (c["name"], c["in"], run, got))
127
+ if len(run) > 1:
128
+ ok(" ".join(run) not in got,
129
+ "中文段被逐字切开(送了 segment)[%s] in=%r 段=%r got=%r"
130
+ % (c["name"], c["in"], run, got))
131
+ # 原型例(任务口径原句)同样对产物断言:中文段逐字保留、英文片段不动
132
+ for t in ("领养 LGBTQ 群体", "AI 记忆 系统", "自我接纳", "蜂群调度 依赖门禁"):
133
+ got = unify_query(t)
134
+ for run in zh_runs(t):
135
+ ok(run in got and " ".join(run) not in got,
136
+ "原型例中文段逐字保留 [%r] 段=%r got=%r" % (t, run, got))
137
+ ok(zh_runs("领养 LGBTQ 群体") == ["领养", "群体"], "中文段抽取判据自检")
138
+
139
+ # ---- 7 · 空白折叠为单空格(段内/段间)----
140
+ ok(unify_query(" beef 报告 ") == "牛肉 报告", "前后空白+多空格折叠")
141
+ ok(unify_query("beef\t报告") == "牛肉 报告", "制表符折叠为单空格")
142
+
143
+ # ---- 8 · 异常降级为原样(不阻断检索主链路)----
144
+ import md_cg.semantic.canonical as _cn # noqa: E402
145
+ _saved = _cn.query_atoms
146
+ _cn.query_atoms = lambda _t: (_ for _ in ()).throw(RuntimeError("boom"))
147
+ try:
148
+ src = "beef 报告"
149
+ ok(unify_query(src) == src, "链路抛异常 → 原样返回")
150
+ finally:
151
+ _cn.query_atoms = _saved
152
+
153
+ # ---- 9 · 中文判据边界(2026-09-30 清理批次乙2):仅长度恰为 1 的串可为真 ----
154
+ # 事实读数(本机实测,见清理批次回执):留池条目 ⑥ 记「`is_zh_char("")` 返回
155
+ # True」——**实测不成立**:`ZH_LO <= ch <= ZH_HI` 是链式比较,对空串即假,
156
+ # 收紧前 `is_zh_char("")` 已返回 False。真实外溢面是**多字符串**——
157
+ # 收紧前 `is_zh_char("中文")` 判真(首字符在区间即真)。两处消费点
158
+ # (unify._runs / 本文件 zh_runs)均逐字符传入,故本收紧对存量行为零变更,
159
+ # 只把判据面(长度恰为 1)钉死。
160
+ ok(is_zh_char("") is False, "空串不是中文(判据面外)")
161
+ ok(is_zh_char("中文") is False, "多字符串为假(首字符在区间亦然)")
162
+ ok(is_zh_char("中a") is False, "多字符串为假(汉字+ASCII 混合)")
163
+ ok(is_zh_char("中") is True, "单汉字为真")
164
+ ok(is_zh_char("\u4e00") is True, "区间下界单汉字为真")
165
+ ok(is_zh_char("\u9fff") is True, "区间上界单汉字为真")
166
+ ok(is_zh_char("\u4dff") is False, "区间下界外单字符为假(U+4DFF)")
167
+ ok(is_zh_char("\ua000") is False, "区间上界外单字符为假(U+A000)")
168
+ ok(is_zh_char("a") is False, "单 ASCII 字母为假")
169
+ # 两侧边界语义一致性:Rust `text::is_zh` 收 char,天然无空/多字符二态;
170
+ # 本侧判据收紧后,「长度 != 1 一律 False」与「按 char 判区间」等价
171
+ # (见 rust/src/text.rs::is_zh docstring 的边界约定段)。
172
+ for ch in ("\u4e00", "\u9fff"):
173
+ ok(is_zh_char(ch) and len(ch) == 1, "单字符区间内 → 真(与 Rust is_zh 等价)%r" % ch)
174
+
175
+ os.environ.pop("MDCG_UNIFY_QUERY", None) # 归还进程环境(本件全程已显式开完)
176
+
177
+ if BAD:
178
+ print("[test_unify_scope] FAILED %d/%d 断言:" % (len(BAD), N))
179
+ for b in BAD:
180
+ print(" - " + b)
181
+ raise SystemExit(1)
182
+ print("[test_unify_scope] %d 断言全绿(fixture %d 例,两侧同源)" % (N, len(FX)))
@@ -6,7 +6,7 @@
6
6
  B 同构聚合:第 2+ 条并入既有(merge_count+1,不新增节点)
7
7
  C 频率限制:窗口内超量 DEFER;其他 role 不受影响
8
8
  D 保护优先:hint≥0.7 不限流
9
- E 层豁免:knowledge 不聚合不限流
9
+ E 层豁免:knowledge 不聚合不限流(限流器未介入;半重复交遗忘闸门判 DEFER)
10
10
  F env 关闭:MDCG_WRITELIMIT=0 放行
11
11
  G 留痕:_forgetting.jsonl 有 limiter 记录
12
12
  H tidy:已落盘同构组聚合 + 成员降权 + 永不删除
@@ -22,6 +22,13 @@ import shutil
22
22
  import tempfile
23
23
  import time
24
24
 
25
+ # 三档自治批次②(2026-10-02)**夹具隔离**:本守卫考的不是档位面(档位守卫 =
26
+ # md_cg/test_autonomy_modes.py),故显式置 full 档——回到改动前「动作直落」的
27
+ # 行为,使本文件的断言意图(合并/覆写/软删真的发生)逐条不变;env 键名从唯一
28
+ # 真源表取(本文件不构成第二处字面量)。
29
+ from . import autonomy_modes as _autonomy_modes # noqa: E402
30
+ os.environ[_autonomy_modes.AUTONOMY_ENV_KEYS["mode"]] = "full"
31
+
25
32
  from .mdcos import MdCGOS
26
33
  from . import writelimit
27
34
 
@@ -122,9 +129,26 @@ check("D1 hint≥0.7 跳过限流(不聚合)",
122
129
  writelimit._save(cg, {"sigs": {}, "rate": {}}) # 清状态隔离本节
123
130
  r_k1 = _write("wl_k1", "知识条目甲乙丙丁1号", layer="knowledge")
124
131
  r_k2 = _write("wl_k2", "知识条目甲乙丙丁2号", layer="knowledge")
125
- check("E1 knowledge 不聚合不限流",
126
- r_k1["verdict"] == "ACCEPT" and r_k2["verdict"] == "ACCEPT",
132
+ # E1 判据按标题所指重编码(issue50-b):层豁免的本体是**限流器未介入**,
133
+ # 而限流器介入的判据是 gate 里带 `limiter` 键(与 G 节 `rec.get("limiter")`
134
+ # 同口径)。原先编码用「r_k2 判 ACCEPT」代理该语义——那是**旧遗忘闸门裁决**的
135
+ # 副产物(半重复被重要度拦成 ACCEPT),不是层豁免的证据,issue50-a 判半重复
136
+ # 为 DEFER 后必然转红。改判据不改意图。
137
+ check("E1 knowledge 层豁免:限流器未介入(两条写入的 gate 均无 limiter 键)",
138
+ r_k1["verdict"] == "ACCEPT"
139
+ and "limiter" not in (r_k1.get("gate") or {})
140
+ and "limiter" not in (r_k2.get("gate") or {}),
127
141
  f"{r_k1.get('gate')}/{r_k2.get('gate')}")
142
+ # E2 钉住新行为(issue50-a 落地后的既成事实):knowledge 层半重复(两条载荷仅
143
+ # 尾字不同,实测 dup=0.7778)由**遗忘闸门**判 DEFER——判据带 redundancy 读数
144
+ # 且无 limiter 键,与限流 DEFER 可区分。
145
+ check("E2 knowledge 层半重复交遗忘闸门判 DEFER(dup=0.7778)",
146
+ r_k2["verdict"] == "DEFER"
147
+ and "redundancy" in (r_k2.get("gate") or {})
148
+ and "limiter" not in (r_k2.get("gate") or {})
149
+ and round(float((r_k2.get("gate") or {})
150
+ .get("redundancy", {}).get("max") or 0), 4) == 0.7778,
151
+ f"{r_k2.get('gate')}")
128
152
 
129
153
  # ---------------------------------------------------------------- F env 关闭
130
154
 
@@ -158,12 +182,15 @@ check("G1 限流留痕 _forgetting.jsonl",
158
182
  # ---------------------------------------------------------------- H tidy
159
183
 
160
184
  writelimit._save(cg, {"sigs": {}, "rate": {}})
161
- os.environ["MDCG_WRITELIMIT"] = "0" # 绕过写入限流,直造存量同构组
162
- try:
163
- for i in range(5):
164
- _write(f"wl_t{i}", f"巡检批次{i}收官记忆")
165
- finally:
166
- os.environ.pop("MDCG_WRITELIMIT", None)
185
+ # 夹具口径(issue50-b,2026-10-01):本节的目的是「**直造存量同构组**」,让 tidy 有已落盘
186
+ # 的存量可聚合(tidy_contextual 按 template_signature(content) 扫索引分组,不依赖限流签名表)。
187
+ # 原夹具用 MDCG_WRITELIMIT=0 绕限流——那只绕过 writelimit;issue50-a 之后**遗忘闸门**在半重复
188
+ # (dup∈[0.60,0.85))处先拦,5 条只落 1 条(实测 nodes=1、tidy dry groups=0),H1/H3/H4 因此
189
+ # 转红。改用 gated=False 直写:它与本节注释的意图「直造存量」逐字相符,且比绕限流更直白——
190
+ # tidy 的作用面本就是**已落盘的存量组**(历史遗留与绕闸写入),写入前的拦截归遗忘闸门。
191
+ # **H 节四条断言一字未动**(改的是夹具造法,不是判据)。
192
+ for i in range(5):
193
+ _write(f"wl_t{i}", f"巡检批次{i}收官记忆", gated=False)
167
194
  dry = writelimit.tidy_contextual(cg, apply=False)
168
195
  check("H1 dry-run 盘出同构组", dry["groups"] >= 1
169
196
  and dry["members"] >= 4, str(dry["groups"]))
@@ -135,9 +135,12 @@ def _run(tmp):
135
135
  "content": "PASSED SKIPME",
136
136
  "layer": "knowledge"})
137
137
  _check("移除即恢复(核心文件零改动)", out.get("committed") is True, repr(out))
138
- # 默认 before 链含 linkref→deps→audit→consistency→gated(2026-09-19 deps 闸门新增)
138
+ # 默认 before 链含 linkref→deps→audit→consistency→gated→autonomy
139
+ # (2026-09-19 deps 闸门新增;2026-10-02 三档自治批次② 档位闸**恒在链尾**
140
+ # ——档位判定必须晚于全部既有资格闸,见 writepipe._gate_autonomy docstring。
141
+ # 断言意图不变:默认链的注册表与执行序 = 这一串。)
139
142
  _check("单例默认链", singleton.names()["before"]
140
- == ["linkref", "deps", "audit", "consistency", "gated"],
143
+ == ["linkref", "deps", "audit", "consistency", "gated", "autonomy"],
141
144
  repr(singleton.names()))
142
145
  _check("单例默认after链", singleton.names()["after"]
143
146
  == ["linkref", "trust"],
package/md_cg/weights.py CHANGED
@@ -468,7 +468,7 @@ def node_importance(cg, nid, indeg=None, red=None, entry=None):
468
468
  "protected_floor": protected}
469
469
 
470
470
 
471
- # 生效条件:给定 cg 且 nodes = cg.index["nodes"] 时按 layer 过滤、limit 为真值才 ids = ids[:int(limit)] 逐节点重算,abs(after-before) < float(min_delta) 记 unchanged;仅 apply=True 才把 frontmatter.importance/importance_source/importance_components 写回(after >= IMPORTANCE_PROTECT 且未 protected 时补写 protected/protection_reason),并向 cg.root 下 append_jsonl(..., MAINTAIN_LOG) 记 batch 后 rebuild_index。
471
+ # 生效条件:给定 cg 且 nodes = cg.index["nodes"] 时按 layer 过滤、limit 为真值才 ids = ids[:int(limit)] 逐节点重算,abs(after-before) < float(min_delta) 记 unchanged(**预演面不接档位闸**);仅 apply=True 才在**任何写盘之前**先经 autonomy_modes.e_gate() 档位判定(补强批次 v1.2·E 面接线)——非 ALLOW(plan 档无计划)即经 forbidden_result() fail-closed 早退(零写盘、零审计行),ALLOW(confirm/full)才把 frontmatter.importance/importance_source/importance_components 写回(after >= IMPORTANCE_PROTECT 且未 protected 时补写 protected/protection_reason),并向 cg.root 下 append_jsonl(..., MAINTAIN_LOG) 记 batch 后 rebuild_index。
472
472
  def recalc(cg, layer=None, limit=None, apply=False, min_delta=APPLY_DELTA,
473
473
  actor="maintain", dry_run_samples=10):
474
474
  """结构重要性重算:覆盖度 + 冗余度 + 验证基底 → 重排节点重要性。
@@ -476,6 +476,11 @@ def recalc(cg, layer=None, limit=None, apply=False, min_delta=APPLY_DELTA,
476
476
  apply=False(默认)只出报表,不写盘(对应计划「可预演」)。
477
477
  apply=True 逐节点改写 `frontmatter.importance`,每批写一条 `_maintain.jsonl`
478
478
  (before/after/分量/actor),并提供 rollback 反向应用。
479
+
480
+ **档位闸(补强批次 v1.2·E 面接线)**:`apply=True` 的**写盘前**先经
481
+ `autonomy_modes.e_gate()`(设计 §三 plan 档 E「须命中计划步骤」)——plan 档
482
+ 无计划 ⇒ `forbidden_result()` fail-closed 早退(零写盘、零审计行);confirm/
483
+ full ⇒ ALLOW、**原链原样**(逐位不变)。`apply=False`(预演)不接闸。
479
484
  """
480
485
  nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
481
486
  indeg = coverage_index(cg)
@@ -519,6 +524,15 @@ def recalc(cg, layer=None, limit=None, apply=False, min_delta=APPLY_DELTA,
519
524
  bl["avg_after"] = 0.0
520
525
  written = 0
521
526
  if apply:
527
+ # 三档自治(补强批次 v1.2·E 面接线,设计 §三 plan 档 E「须命中计划步骤」):
528
+ # E 类动作**写盘前**的档位判定单点(同 `freshness.recalc` 口径)——
529
+ # 本函数无独立资格闸,闸口取「进入写盘支路的第一行」=任何盘面写入之前;
530
+ # ALLOW(confirm/full)原链原样,plan 档无计划 ⇒ fail-closed 早退。
531
+ from . import autonomy_modes as _am
532
+ _dec = _am.e_gate()
533
+ if _dec["decision"] != _am.ALLOW:
534
+ return _am.forbidden_result(_dec, action="importance", dry_run=False,
535
+ written=0, batch=batch)
522
536
  for plan in planned:
523
537
  nid = plan["node_id"]
524
538
  node = cg.get(nid)
@@ -7,6 +7,14 @@
7
7
  衰减率 γ 按对象配置;任何在本模块之外出现的衰减核实现
8
8
  (exp(-t/τ)、×(1-factor)、EMA 保持率)都是 bug——
9
9
  由 tools/time_core_lint.py 按 E5 口径机械化审计。
10
+ (P3 落地:该审计器此前全仓不存在,现落在 **`md_cg/test_time_core_lint.py`**
11
+ ——`python -X utf8 -m md_cg.test_time_core_lint` 审计全仓四形态,指数核的
12
+ **调用式 `exp(-…` 与点调用 `.exp(…)/.exp()`(Rust 写法)两种形态都入面**;
13
+ 另带登记表 + 等价性断言:`links.py` 同族半衰期(G2),以及 **Rust 读侧
14
+ `rust/src/freshness.rs` 的指数核**(G11——从 Rust 源码抽常量与核表达式逐 Δt
15
+ 点与 `cred_factor` 对拍,因 Rust 无法被 Python 调用)。自带 `--mutate` 定点
16
+ 变异自证,其中 P4-M9 就是「把该 Rust 登记摘掉必须转红」那条。
17
+ **不新开顶层 `tools/` 目录**:那会改 `WORKSPACE_INDEX.md` 的结构面。)
10
18
  规范依据:docs/概念钉死批_GPT四点评审_v0.1.md 钉死 3。
11
19
  """
12
20
  import math
@@ -20,7 +20,8 @@ write 的六道闸(audit 校验 / consistency 冲突 / review 审核 / gated
20
20
  绕过(反面清单:不学 pi 的全权信任,信任必须结构强制)。
21
21
 
22
22
  默认链(install_default_gates,与重构前 _cg_dispatch write 分支行为逐字
23
- 节一致):audit → consistency → gated → _executor。
23
+ 节一致):audit → consistency → gated → **autonomy(三档自治档位闸,
24
+ 2026-10-02 批次②)** → _executor。
24
25
 
25
26
  验收口径(交接文档 §3⑥):全部既有写入测试零改动通过;新增/移除一个
26
27
  拦截器不改核心文件(register_before / unregister_before 即插即拔)。
@@ -401,6 +402,12 @@ def _gate_gated(ctx):
401
402
  out["moved_to"] = "merged_into:" + str(res.get("merged_into"))
402
403
  elif v in ("DROP", "DEFER"):
403
404
  out["moved_to"] = v.lower()
405
+ elif v == "CONFIRM":
406
+ # 三档自治批次②:变更确认档下 B 合并已出变更单(未合并)——去向如实
407
+ # 透出 review_queue(DROP/DEFER 两态的字面量分支一字未动)。
408
+ out["moved_to"] = res.get("moved_to") or "review_queue"
409
+ out["mutation"] = res.get("mutation")
410
+ out["autonomy"] = res.get("autonomy")
404
411
  # gated 是**替代落盘路径**(自行落盘/合并后直接返回终态、不跑 after 链),故
405
412
  # 一跳同步传播须在此单独触发——否则 MERGE 类覆写会漏传下游(非对称边界,
406
413
  # 与 `_after_trust` 注释互指)。
@@ -592,6 +599,89 @@ def _executor(ctx):
592
599
  return out
593
600
 
594
601
 
602
+ # ---- 三档自治闸(设计 v0.2 §三 · 批次②) ----------------------------------
603
+
604
+ #: 改写单要复现「原动作」所需的载荷键(**写链 payload 白名单**):变更单裁决
605
+ #: accept 时按此重放 cg.add,使「直接写」与「出单→确认→写」两条路径落盘的
606
+ #: 节点 fm 等价(issue50-c F1 的同一教训:入队时丢声明 = 两条路径元数据不等价)。
607
+ #: `sensitivity`/`layer` 不在列(各自有专属槽:rec.sensitivity / rec.layer);
608
+ #: `override` 在列(受保护节点的覆写授权是原动作的一部分,不能替调用方补)。
609
+ _AUTONOMY_META_KEYS = ("tags", "condition_space", "verification_basis",
610
+ "non_applicable_conditions", "derived_from", "relation",
611
+ "depends_on", "valid_from", "valid_until",
612
+ "verification_state", "importance", "importance_source",
613
+ "override")
614
+
615
+
616
+ # 生效条件:ctx 的 a/cg/nid 就绪时以 forgetting.prior_node 判「同 id 已存在」(存在=C 改写、不存在=A 新增)并交 autonomy_modes.decide 判定——ALLOW 返回 None 放行(与改动前同一条链);FORBID 返回 ok=False/moved_to="autonomy_forbidden" 的终态(fail-closed,不落盘、不出单);CONFIRM 经 autonomy_modes.propose_mutation 出变更单(复用既有 propose 单点)并返回 ok=True/committed=False/moved_to="review_queue" 的终态(**不落盘**);
617
+ def _gate_autonomy(ctx):
618
+ """档位闸:`cg(op=write)` 对**既有 node_id 的覆写**(C 改写)在变更确认档出单。
619
+
620
+ 位置(设计 §三 硬约束②「纯加严」):before 链**末位**——audit / consistency
621
+ / gated 三道既有资格闸全部放行之后、链尾执行器落盘之前。故:
622
+ · 档位**不参与**资格判定(内容政策/冲突/限流/遗忘裁决一律照旧先跑);
623
+ · 档位**不放宽**任何既有判据(放行分支就是「返回 None」= 原链原样);
624
+ · 覆写面的其余资格闸(层闸 + 写保护闸 + 降级闸)住在 `cg.add` 内部,
625
+ 故出单**之前**先经 `cg.write_qualify`(同一批 protect 单点,只判不写)
626
+ 跑一遍——受保护/越权覆写照旧当场被拒,不会变成「静默入队」。
627
+
628
+ 动作类判定:`forgetting.prior_node` 同 id 存在 ⇒ **C 改写**;不存在 ⇒
629
+ **A 新增**(A 在缺省档与会话档都放行,只有计划档会 fail-closed 拦下——
630
+ 设计 §三「计划外零变更」)。判据复用既有单点,不另写一份存在性判据。
631
+
632
+ 不落盘的边界(如实):`gated=true` 的写入在上游 `_gate_gated` 已是**替代
633
+ 执行路径**(自行落盘/合并后直接返回终态、不走本闸),故本闸不覆盖它;
634
+ 该面的档位判定由 `remember_gated` 自己的单点承担——**C 改写 / A 新增**在
635
+ `MdCGOS._autonomy_gate_rewrite`、**B 合并**在 `MdCGOS._autonomy_gate_merge`。
636
+ 本闸只覆盖走链尾执行器的直写(`gated=false` 的 `cg(op=write)`)。
637
+ 三处共用同一张矩阵(`autonomy_modes.decide`),不各写一份判据。
638
+
639
+ 订正记录(2026-10-02,补强批次):本句此前写「其覆写/合并分别由
640
+ `remember_gated` 的 C/B 判定覆盖」——B 合并确已覆盖,**C 覆写当时没有**
641
+ (该分支直调 `self.add`),属失实陈述(独立复核发现 1 的代码根据)。
642
+ 补强批次在 `_autonomy_gate_rewrite` 落码后本句才与实现逐句一致。
643
+ """
644
+ a = ctx["a"]
645
+ cg = ctx["cg"]
646
+ nid = ctx["nid"]
647
+ from . import autonomy_modes as _am
648
+ from . import forgetting as _forgetting
649
+ prior = _forgetting.prior_node(cg, nid)
650
+ action = _am.C_REWRITE if prior is not None else _am.A_ADD
651
+ if prior is not None:
652
+ # 资格在先(纯加严):覆写的层闸/写保护闸住在 `cg.add` 内部,若档位闸
653
+ # 先出单,一次本该 `AccessDenied`/`ProtectionError` 的覆写会变成静默入队
654
+ # ——那是放宽既有判据。故按**同一批 protect 单点**先跑一遍资格(只判不写)。
655
+ cg.write_qualify(nid, target_layer=a.get("layer"),
656
+ override=bool(a.get("override")),
657
+ actor=getattr(cg, "actor", None))
658
+ dec = _am.decide(action)
659
+ if dec["decision"] == _am.ALLOW:
660
+ return None
661
+ # 档位判定读数(**不参与资格判定**,只如实透出档位/动作类/判定)
662
+ _aut = {"mode": dec["mode"], "action": dec["action"],
663
+ "action_name": dec["action_name"], "decision": dec["decision"]}
664
+ if dec["decision"] == _am.FORBID:
665
+ return {"ok": False, "id": nid, "committed": False,
666
+ "moved_to": "autonomy_forbidden", "error": "autonomy_forbid",
667
+ "autonomy": _aut, "verdict": ctx.get("verdict"),
668
+ "hint": dec["hint"]}
669
+ meta = {k: a[k] for k in _AUTONOMY_META_KEYS if a.get(k) is not None}
670
+ pay = _am.mutation_payload(
671
+ action, nid, after=a.get("content", ""),
672
+ reason="写链覆写(cg(op=write) 对既有节点 %s 的 %s)"
673
+ % (nid, _am.ACTION_NAMES[action]),
674
+ primitive="add", meta=meta)
675
+ pr = _am.propose_mutation(cg, action, nid, payload=pay,
676
+ layer=a.get("layer") or "knowledge",
677
+ sensitivity=a.get("sensitivity"), info=True)
678
+ return {"ok": True, "id": nid, "pid": pr["pid"], "committed": False,
679
+ "moved_to": "review_queue", "autonomy": _aut, "mutation": pay,
680
+ "verdict": ctx.get("verdict"),
681
+ "hint": ("%s 本次未落盘:变更单已入审核队列(pid=%s,目标 %s)。"
682
+ % (dec["hint"], pr["pid"], nid))}
683
+
684
+
595
685
  # 生效条件:value 传入即无条件延迟导入并转调 mcp_server._split_ids 后原样返回其结果(本符号无自身分支);
596
686
  def _split_ids(value):
597
687
  # 与 mcp_server._split_ids 同源(延迟导入,单一真源)
@@ -612,9 +702,12 @@ def install_default_gates(pipe):
612
702
 
613
703
  链序(2026-09-19 起):
614
704
  before = linkref(解析) → deps(依赖声明) → audit → consistency → gated
615
- → 链尾执行器
705
+ → autonomy(档位) → 链尾执行器
616
706
  after = linkref(建边) → trust(一跳传播)
617
707
 
708
+ autonomy(三档自治批次②)恒在最末:档位判定只决定「立即落」还是「出变更
709
+ 单」,必须晚于全部既有资格闸(纯加严,设计 §三 硬约束②)。
710
+
618
711
  linkref 置于链首的理由:正文引用解析是**纯读、无副作用**,且其结果必须
619
712
  先于任何短路闸写入 ctx,供 after 链消费。短路闸(REJECT/DEFER/gated)
620
713
  返回终态时 `execute` 不跑 after 链,故未落盘的写入不会建边——语义正确。
@@ -638,6 +731,12 @@ def install_default_gates(pipe):
638
731
  pipe.register_before("audit", _gate_audit)
639
732
  pipe.register_before("consistency", _gate_consistency)
640
733
  pipe.register_before("gated", _gate_gated)
734
+ # 档位闸**链尾**(三档自治批次②,设计 §三 硬约束②):既有资格闸全部通过
735
+ # 之后、链尾执行器落盘之前——档位只决定「立即落」还是「出变更单」,不参与
736
+ # 资格判定。gated 分支是替代执行路径(自行落盘并已 return),其合并/覆写由
737
+ # remember_gated 的 B/C 判定覆盖(`_autonomy_gate_merge` / 补强批次的
738
+ # `_autonomy_gate_rewrite`),故本闸登记在 gated 之后即可。
739
+ pipe.register_before("autonomy", _gate_autonomy)
641
740
  pipe.register_after("linkref", linkref.after_hook())
642
741
  pipe.register_after("trust", _after_trust)
643
742
  return pipe
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@furongjun1999/dsh-memory",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "description": "灵枢(Lingshu·líng shū)DeepSeek Harness 插件:完整大脑——长期记忆/知识飞轮/自我认知/递归反思接入 DSH,对话自动沉淀进 md_cg 认知图(md 文档)",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -20,6 +20,7 @@
20
20
  "lib",
21
21
  "src",
22
22
  "md_cg",
23
+ "utf8_boot.py",
23
24
  "data/policy.json",
24
25
  "skills",
25
26
  "README.md",
@@ -85,7 +86,7 @@
85
86
  "build": "node -e \"require('node:fs').rmSync('lib',{recursive:true,force:true})\" && tsc -p tsconfig.json",
86
87
  "test": "npm run build && node --import tsx --test \"test/*.test.ts\"",
87
88
  "prepare": "node -e \"const fs=require('fs');const tsc='node_modules/typescript/bin/tsc';fs.existsSync(tsc)?(fs.rmSync('lib',{recursive:true,force:true}),require('child_process').execFileSync(process.execPath,[tsc,'-p','tsconfig.json'],{stdio:'inherit'})):console.log('[prepare] 跳过构建:typescript 未安装(NODE_ENV=production 或 --omit=dev 会省略 devDependencies)——需要构建请用 npm install --include=dev')\"",
88
- "gate": "python scripts/check_unreachable.py && python scripts/check_publish_artifact.py && python scripts/cogmap_sync.py check && python scripts/link_check.py && python scripts/workspace_index.py --check && python scripts/verify_discipline.py --allow-missing && python scripts/gate_rust_crate_test.py && python scripts/gate_rust_parity.py",
89
+ "gate": "python scripts/check_unreachable.py && python scripts/check_publish_artifact.py && python scripts/check_publish_smoke.py && python scripts/cogmap_sync.py check && python scripts/link_check.py && python scripts/workspace_index.py --check && python scripts/verify_discipline.py --allow-missing && python scripts/gate_rust_crate_test.py && python scripts/gate_rust_parity.py",
89
90
  "prepublishOnly": "npm run build && npm test && npm run gate"
90
91
  },
91
92
  "repository": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "lingshu-skills",
4
- "version": "0.6.1",
4
+ "version": "0.7.1",
5
5
  "description": "灵枢(AEIS)自我认知技能包——白箱条件化知识(Agent Skills 兼容导出)。本质是灵枢了解自身的工具:每个技能描述灵枢在什么条件下能做什么、怎么执行、克制什么(KCCS 四要素),由条件路由图精确路由,由灵枢 MCP 工具执行验证(物理基底)。",
6
6
  "author": {
7
7
  "name": "灵枢(AEIS)· CommonTrustProtocol"