@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
@@ -180,7 +180,9 @@ TOOLS = [
180
180
  "「跳过超大、继续试更小的」在预算紧张时淘汰最有价值的详实条目。"
181
181
  "会话开始或重要工作前调用。可选启用第 5 路模糊召回(分级隶属度)"
182
182
  "与第 6 路条件语义路(条件结构驱动),并注入调用方 LLM 的查询"
183
- "扩展词(索引侧始终白箱)。",
183
+ "扩展词(索引侧始终白箱)。缺省另含第 7 路因果路(沿 edges 多跳)"
184
+ "与第 8 路时间路(时间邻近度)——按裁定「全进默认检索」缺省开,"
185
+ "可用 causal=false / temporal=false 单独关。",
184
186
  "inputSchema": _s("", query=_p("string", "描述当前任务的查询", True),
185
187
  budget_tokens=_p("integer", "token 预算(默认 1200)"),
186
188
  max_item_tokens=_p("integer", "单条上限(默认 250);超限条目截断纳入。"
@@ -203,12 +205,22 @@ TOOLS = [
203
205
  goal=_p("string", "当前目标(第 5 篇第 3 章):启用 goal 路给召回定向;"
204
206
  "省略则自动取活跃目标"),
205
207
  goal_path=_p("boolean", "启用目标定向路(默认否;给 goal 即自动启用)"),
208
+ causal=_p("boolean", "因果路(P3,设计稿 §6.2):以词法/实体命中"
209
+ "为种子沿 edges 多跳扩散,**缺省开**(全进默认"
210
+ "检索);传 false 单独关本路。无 edges 的库上"
211
+ "自然为空"),
212
+ temporal=_p("boolean", "时间路(P3,设计稿 §6.3):按时间邻近度"
213
+ "排序的排名项,核走 time_core.cred_factor;"
214
+ "**缺省开**;传 false 单独关本路。无时间算子/"
215
+ "区间时恒空"),
206
216
  include_recent=_p("boolean", "是否附「近期事件」窗口(默认否)"),
207
217
  recent_limit=_p("integer", "近期事件条数(默认 10)"),
208
218
  fusion=_p("string", "融合模式:sum(经典 RRF,奖励多路共识)"
209
219
  "| max(取各路最高贡献,不奖励共识)。"
210
220
  "fuzzy=true 时缺省 max——实测 sum 会低估"
211
- "「只有模糊路捞到」的目标,self@1 −10.1%")),
221
+ "「只有模糊路捞到」的目标,self@1 −10.1%;"
222
+ "因果路(causal,缺省开)同为单路独有召回型,"
223
+ "故一并缺省 max")),
212
224
  },
213
225
  {
214
226
  "name": "mdcg_search",
@@ -1567,6 +1579,7 @@ def _sustain_call(cg, a):
1567
1579
  路线图「常驻服务:会话/心跳/自愈」。原则:诊断只读;自愈只碰派生物
1568
1580
  (索引/临时文件/日志边界),永不删节点;缺密钥属于权限事实,只报告不修。
1569
1581
  """
1582
+ from . import sleep as _sleep
1570
1583
  from . import sustain
1571
1584
  act = (a.get("action") or "status").strip().lower()
1572
1585
  name = a.get("name") or os.environ.get("MDCG_SUSTAIN_NAME") or "md_cg"
@@ -1627,16 +1640,27 @@ def _sustain_call(cg, a):
1627
1640
  or sustain.DEFAULT_BEAT_INTERVAL),
1628
1641
  heal_interval=float(a.get("heal_interval")
1629
1642
  or sustain.DEFAULT_HEAL_INTERVAL),
1630
- auto_heal=bool(a.get("auto_heal", True)),
1643
+ # 四档 auto_* 一律经 sustain 的**单一真源读取器**(P0-2)——此处
1644
+ # 不得再写缺省字面量:op 路径原 `auto_tidy` 缺省 False 与 env 路径
1645
+ # 的 True 相反,同 (root,name) 走两条入口语义不一致。显式入参仍优先。
1646
+ auto_heal=sustain.auto_from_args("auto_heal", a),
1631
1647
  scrub_interval=float(a.get("scrub_interval")
1632
1648
  or sustain.DEFAULT_SCRUB_INTERVAL),
1633
- auto_scrub=bool(a.get("auto_scrub", False)),
1649
+ auto_scrub=sustain.auto_from_args("auto_scrub", a),
1634
1650
  evolve_interval=float(a.get("evolve_interval")
1635
1651
  or sustain.DEFAULT_EVOLVE_INTERVAL),
1636
- auto_evolve=bool(a.get("auto_evolve", False)),
1652
+ auto_evolve=sustain.auto_from_args("auto_evolve", a),
1637
1653
  tidy_interval=float(a.get("tidy_interval")
1638
1654
  or sustain.DEFAULT_TIDY_INTERVAL),
1639
- auto_tidy=bool(a.get("auto_tidy", False)))
1655
+ auto_tidy=sustain.auto_from_args("auto_tidy", a),
1656
+ # 第六档睡眠周期(§4.7):五个值一律经 `md_cg/sleep.py` 的**单一真源
1657
+ # 读取器**(此处不得再写缺省字面量,也不得再写 env 键名字面量——
1658
+ # 键名真源是 `sleep.SLEEP_ENV_KEYS`)。与 env 路径逐字同一组调用。
1659
+ sleep_interval=_sleep.sleep_interval(),
1660
+ auto_sleep=_sleep.sleep_enabled(),
1661
+ sleep_merge=_sleep.sleep_merge_mode(),
1662
+ sleep_window=_sleep.sleep_window(),
1663
+ sleep_scrub_apply=_sleep.sleep_scrub_apply())
1640
1664
  return {"loop": lp.start().status()}
1641
1665
  if act in ("stop", "down"):
1642
1666
  lp = sustain.get_loop(cg, name)
@@ -2047,7 +2071,7 @@ def _task_call(cg, a):
2047
2071
  return _t.list_tasks(cg, status=tstat or None, limit=a.get("limit"))
2048
2072
 
2049
2073
 
2050
- # 生效条件:op 取 a.get("op") 转 str 去空白并 lower;缺失时按参数签名回推(含 content→write / query|node_id→read / intent→route;无签名可推时维持 read),op=="help" 在角色闸门之前直通 _help_call;其余 op 先经 cg.principal.require_op(op)(越权即 AccessDenied)再按 alias→内联名→cli 表分派;未识别 op 抛 ValueError;
2074
+ # 生效条件:op 取 a.get("op") 转 str 去空白并 lower;缺失时按参数签名回推(含 content→write / query|node_id→read / intent→route;无签名可推时维持 read),op=="help" 在角色闸门之前直通 _help_call;其余 op 先经 cg.principal.require_op(op)(越权即 AccessDenied)再按 alias→内联名→cli 表分派;未识别 op 抛 ValueError;fork:op=="forget" 的**删除**分支走 cg.forget_gated(三档自治批次②:档位判定在既有资格闸之后,变更确认档出变更单且不软删;restore 分支不变),op=="write" 经 writepipe 默认链(链尾 autonomy 档位闸);
2051
2075
  def _cg_dispatch(cg, a):
2052
2076
  """认知图唯一入口的 op 分发主体。"""
2053
2077
  op = (a.get("op") or "read").strip().lower()
@@ -2220,6 +2244,21 @@ def _cg_dispatch(cg, a):
2220
2244
  h["write_policy"] = audit.unavailable_report(_pe)
2221
2245
  h["theory"] = _th.check()
2222
2246
  h["links"] = _lk.ls()
2247
+ # 代校验(纯增量,2026-09-30):长驻进程可被问「你现在这一代是不是盘面那一代」。
2248
+ # 根因(第4条取证):升级后长驻进程仍持有启动代模块,与升级后惰性导入的新代
2249
+ # 模块跨代混用(实测 cg 写入 ImportError: cannot import name 'mint_auto_id'
2250
+ # from 'md_cg.mdcg',而盘面正常)——盘面无从自证,须由进程自报。
2251
+ # 落点=generation.report()(单点):code_generation / disk_generation /
2252
+ # stale_on_disk / restart_required / hint。**纯增量**:既有键名、键值、
2253
+ # 嵌套结构一字不动(本行只追加五个新键,键名与既有键无冲突)。
2254
+ # 失败不得把 op=info 拖崩(同为诊断面契约):异常在此收口成不可用形状。
2255
+ from . import generation as _gen
2256
+ try:
2257
+ h.update(_gen.report())
2258
+ except Exception as _ge: # noqa: BLE001
2259
+ h.update({"code_generation": None, "disk_generation": None,
2260
+ "stale_on_disk": None, "restart_required": None,
2261
+ "hint": "代校验不可用:%s: %s" % (type(_ge).__name__, _ge)})
2223
2262
  return h
2224
2263
 
2225
2264
  if op == "route":
@@ -2369,8 +2408,11 @@ def _cg_dispatch(cg, a):
2369
2408
  if op == "forget":
2370
2409
  if (a.get("action") or "forget").strip().lower() == "restore":
2371
2410
  return cg.restore(a.get("node_id", ""), force=bool(a.get("force")))
2372
- return cg.forget(a.get("node_id", ""), a.get("reason", ""),
2373
- override=bool(a.get("override")))
2411
+ # 三档自治批次②(设计 §三 D 删除):工具面经 forget_gated——档位判定在
2412
+ # 既有资格闸(not_found / protect.guard_forget)**之后**,变更确认档
2413
+ # 出变更单且不软删;库层 forget 语义不变(脚本通道不接,本批边界)。
2414
+ return cg.forget_gated(a.get("node_id", ""), a.get("reason", ""),
2415
+ override=bool(a.get("override")))
2374
2416
 
2375
2417
  if op == "protect":
2376
2418
  return _protect_call(cg, a)
@@ -3085,7 +3127,7 @@ def _whitebox_call(cg, a):
3085
3127
  return whitebox.dispatch(cg, a)
3086
3128
 
3087
3129
 
3088
- # 生效条件:op=(a.get("op") or "").strip().lower();op=="relation" 时返回 stg.relation(cg, a.get("a",""), a.get("b",""), time_axis=axis);op=="timeline" 时返回 stg.timeline(cg, layer=a.get("layer"), limit=int(a.get("limit") or 50), desc=bool(a.get("desc", True)), time_axis=axis, session=a.get("session"))(session 缺省不过滤读遍所有会话,"*" = 显式跨会话,其它值 = 本会话视图);op=="anchors" 时返回 stg.anchors(cg, time_window=a.get("time_window"), bbox=a.get("bbox"), layer=a.get("layer"), limit=int(a.get("limit") or 50));op=="consistency" 时返回 stg.consistency(cg, layer=a.get("layer"), limit=int(a.get("limit") or 50));op 为空或其它的值抛 ValueError。
3130
+ # 生效条件:op=(a.get("op") or "").strip().lower();op=="relation" 时返回 stg.relation(cg, a.get("a",""), a.get("b",""), time_axis=axis);op=="timeline" 时返回 stg.timeline(cg, layer=a.get("layer"), limit=int(a.get("limit") or 50), desc=bool(a.get("desc", True)), time_axis=axis, session=a.get("session"))(session 缺省不过滤读遍所有会话,"*" = 显式跨会话,其它值 = 本会话视图);op=="anchors" 时返回 stg.anchors(cg, time_window=a.get("time_window"), bbox=a.get("bbox"), layer=a.get("layer"), limit=int(a.get("limit") or 50));op=="consistency" 时返回 stg.consistency(cg, layer=a.get("layer"), limit=int(a.get("limit") or 50));op 为空或其它的值抛 ValueError;返回体**原样透传**——stg 三接口的 truncated/scanned/kept/hint 读数与修正后的 count 语义(条件命中总数,issue #52)不经本层裁剪或改写,max_scan 本批不开显式入参。
3089
3131
  def _stg_call(cg, a):
3090
3132
  """语义时空图唯一入口。
3091
3133
 
@@ -3094,6 +3136,13 @@ def _stg_call(cg, a):
3094
3136
  的透传策略**相反**——`cg` 侧缺省由库层定(不启用的时间算子),`stg` 侧轴
3095
3137
  决定「排序/关系依据」必须**恒有值**,故在此填默认;非法轴仍由
3096
3138
  `trust.time_axis_of` fail-closed 抛错(本层不预先白名单,避免两套枚举)。
3139
+
3140
+ 返回体**原样透传**(issue #52):timeline/anchors/consistency 新增的
3141
+ `truncated` / `scanned` / `kept` 读数与截断时的 `hint` 随返回值自动带出,
3142
+ 本层不裁剪、不改写;`count` 语义已是**条件命中总数**(不再受索引切片影响,
3143
+ 在役对照 `timeline(session=…)` 由 25 恢复为 229)。`max_scan` 本批不在
3144
+ MCP 面开显式入参(契约:数值不是修法——需要收窄时细化生效条件/
3145
+ 不适用条件,或按设计稿建立条件索引)。
3097
3146
  """
3098
3147
  from . import stg
3099
3148
  op = (a.get("op") or "").strip().lower()
@@ -3229,7 +3278,26 @@ _MDCG_OP_REQUIRE = {
3229
3278
  }
3230
3279
 
3231
3280
 
3232
- # 生效条件:name 为已注册工具名之一(cg / stg / mdcg_whitebox / mdcg_service_info / mdcg_remember 等);name 属 _MDCG_OP_REQUIRE 且 cg.principal 具 require_op 属性时先 require_op(越权抛 AccessDenied),principal 为 None 或无该方法时跳过;cg 走 _cg_call、stg 走 _stg_call、whitebox 走 _whitebox_call;未识别的 name 返回含 error 的响应字典而不抛异常,进程不因此中断;
3281
+ # 生效条件:四个布尔入参任意组合下无条件返回 "max"(当 use_fuzzy 或 use_semantic 或 use_goal 或 use_causal 任一为真)或 None(全为假);use_temporal 不参与判据;不做 IO、不看环境变量;
3282
+ def recall_fusion_default(use_fuzzy, use_semantic, use_goal, use_causal,
3283
+ use_temporal=False):
3284
+ """`mdcg_recall` 的**缺省融合口径**判据(唯一单点,供守卫断言)。
3285
+
3286
+ P3-遗留 A(2026-10-01 补披露):`use_causal` **缺省 True** ⇒ 默认 recall 的
3287
+ 融合口径由 `sum` 变成 **`max`**(影响**所有**默认召回调用,与库是否有边无关)。
3288
+ 该口径符合设计稿 §6.2「因果路融合缺省 max」与既有先例表达式
3289
+ (fuzzy/semantic/goal 同款),故**保留**;本函数只把那个表达式从调用点提为
3290
+ **可断言的名字**——否则「缺省到底是 sum 还是 max」只能靠读表达式,
3291
+ 静默漂移(少写一个 `or use_causal`)无守卫可抓。
3292
+
3293
+ `use_temporal` **刻意不入判据**:时间路是**排名项**(§6.3(e):与 S4 层级
3294
+ 激活同类,不是单路独有召回型),它开着也不改变融合口径。
3295
+ """
3296
+ return ("max" if (use_fuzzy or use_semantic or use_goal or use_causal)
3297
+ else None)
3298
+
3299
+
3300
+ # 生效条件:name 为已注册工具名之一(cg / stg / mdcg_whitebox / mdcg_service_info / mdcg_remember 等);name 属 _MDCG_OP_REQUIRE 且 cg.principal 具 require_op 属性时先 require_op(越权抛 AccessDenied),principal 为 None 或无该方法时跳过;cg 走 _cg_call、stg 走 _stg_call、whitebox 走 _whitebox_call;未识别的 name 返回含 error 的响应字典而不抛异常,进程不因此中断;mdcg_forget 走 cg.forget_gated(同 cg(op=forget) 的档位路径)、mdcg_remember 的非 gated 直写分支在同 id 覆写时先过 cg.write_qualify 再按档位判定(三档自治批次②);
3233
3301
  def _dispatch(cg, name, args):
3234
3302
  a = args or {}
3235
3303
  # P1-1:mdcg_* 面的角色作用域闸(与 cg 工具的 require_op 同一语义——
@@ -3296,6 +3364,51 @@ def _dispatch(cg, name, args):
3296
3364
  _prior_cov = (_forgetting.self_coverage(cg, _prior, _content)
3297
3365
  if _prior is not None else None)
3298
3366
  _dup = _forgetting.redundancy(cg, _content, layer=_layer, exclude=nid)
3367
+ # 三档自治批次②(设计 §三):本分支是**热写入路径的第二落点**(另两处 =
3368
+ # writepipe 链尾执行器、remember_gated)——同 id 已存在 ⇒ C 改写、
3369
+ # 不存在 ⇒ A 新增,判据复用 forgetting.prior_node(不另写一份存在性判据)。
3370
+ # 位置:既有 `if a.get("gated")` 分支之后、`cg.add` 之前(直写面没有别的
3371
+ # 资格闸;audit/限流属 gated 分支的链)。确认档 ⇒ 出单且不落盘。
3372
+ from . import autonomy_modes as _am
3373
+ _action = _am.C_REWRITE if _prior is not None else _am.A_ADD
3374
+ if _prior is not None:
3375
+ # 资格在先(纯加严):见 writepipe._gate_autonomy 同款注释——覆写的
3376
+ # 层闸/写保护闸在 cg.add 内部,档位闸不得抢在它们之前把拒绝变成入队。
3377
+ cg.write_qualify(nid, target_layer=_layer,
3378
+ override=bool(a.get("override")),
3379
+ actor=getattr(cg, "actor", None))
3380
+ _dec = _am.decide(_action)
3381
+ if _dec["decision"] != _am.ALLOW:
3382
+ _aut = {"mode": _dec["mode"], "action": _dec["action"],
3383
+ "action_name": _dec["action_name"],
3384
+ "decision": _dec["decision"]}
3385
+ if _dec["decision"] == _am.FORBID:
3386
+ return {"ok": False, "id": nid, "committed": False,
3387
+ "moved_to": "autonomy_forbidden",
3388
+ "error": "autonomy_forbid", "autonomy": _aut,
3389
+ "hint": _dec["hint"]}
3390
+ _meta = {k: a[k] for k in
3391
+ ("tags", "condition_space", "verification_basis",
3392
+ "non_applicable_conditions", "derived_from", "relation",
3393
+ "role", "importance") if a.get(k) is not None}
3394
+ # 直写面的裁决参数(本分支的 add 会自己跑冲突闸):随单落进复现 meta,
3395
+ # 否则「确认后落盘」与「直接落盘」两条路径的判据不等价。
3396
+ _meta["consistency"] = bool(a.get("consistency", True))
3397
+ _meta["on_conflict"] = a.get("on_conflict") or "reject"
3398
+ _meta["override"] = bool(a.get("override"))
3399
+ _pay = _am.mutation_payload(
3400
+ _action, nid, after=_content,
3401
+ reason="mdcg_remember 直写(%s)" % _am.ACTION_NAMES[_action],
3402
+ primitive="add", meta=_meta)
3403
+ _pr = _am.propose_mutation(cg, _action, nid, payload=_pay,
3404
+ layer=_layer,
3405
+ sensitivity=a.get("sensitivity"),
3406
+ info=True)
3407
+ return {"ok": True, "id": nid, "committed": False,
3408
+ "moved_to": "review_queue", "pid": _pr["pid"],
3409
+ "mutation": _pay, "autonomy": _aut,
3410
+ "hint": "%s 本次未落盘:变更单已入审核队列(pid=%s,目标 %s)。"
3411
+ % (_dec["hint"], _pr["pid"], nid)}
3299
3412
  written = cg.add(nid, _content, layer=_layer,
3300
3413
  # B2:同上——落盘面丢字段=上游声明静默失效。
3301
3414
  sensitivity=a.get("sensitivity"),
@@ -3332,7 +3445,16 @@ def _dispatch(cg, name, args):
3332
3445
  use_fuzzy = bool(a.get("fuzzy"))
3333
3446
  use_semantic = bool(a.get("semantic"))
3334
3447
  use_goal = bool(a.get("goal_path") or a.get("goal"))
3335
- if use_fuzzy or use_semantic or use_goal:
3448
+ # P3(裁定 9「全进默认检索」):因果路(chain)/ 时间路(temporal)**进缺省集**,
3449
+ # 但**每路可单独关**——显式传 causal=false / temporal=false 即从本次调用剔除。
3450
+ # 未显式传(None)= 缺省进路;与既有 fuzzy/semantic/goal 的「显式启用」语义相反,
3451
+ # 这是刻意的非对称:这两路在无 edges / 无时间参数的库上自然为空,缺省开不产噪声。
3452
+ use_causal = a.get("causal")
3453
+ use_temporal = a.get("temporal")
3454
+ use_causal = True if use_causal is None else bool(use_causal)
3455
+ use_temporal = True if use_temporal is None else bool(use_temporal)
3456
+ if (use_fuzzy or use_semantic or use_goal
3457
+ or not use_causal or not use_temporal):
3336
3458
  paths = ["lexical", "bucket", "entity", "graph"]
3337
3459
  if use_fuzzy:
3338
3460
  paths.append("fuzzy")
@@ -3340,13 +3462,22 @@ def _dispatch(cg, name, args):
3340
3462
  paths.append("semantic")
3341
3463
  if use_goal:
3342
3464
  paths.append("goal")
3465
+ if use_causal:
3466
+ paths.append("chain")
3467
+ if use_temporal:
3468
+ paths.append("temporal")
3343
3469
  paths = tuple(paths)
3344
3470
  else:
3345
- paths = None
3471
+ paths = None # 缺省六路(mdcos.search_rrf 的缺省集)
3346
3472
  # fuzzy 路缺省用 max 融合:实测(memory-bench-1000,870 查询)sum 会把
3347
3473
  # self@1 拉低 10.1%,因为求和奖励「多路共识」、低估「模糊路独有」的目标。
3348
3474
  # semantic 路同理:条件结构命中常是「独有召回」,故一并缺省 max。
3349
- fusion = a.get("fusion") or ("max" if (use_fuzzy or use_semantic or use_goal) else None)
3475
+ # P3:因果路按设计稿 §6.2「融合口径」同为**单路独有召回型**,照抄本条先例
3476
+ # 形态(同一表达式内追加条件,不另起第二套判据)。
3477
+ # P3-遗留 A(2026-10-01):表达式提为 `recall_fusion_default` 单点,
3478
+ # 使「缺省口径 = max」可被守卫直接断言(防静默漂移)。
3479
+ fusion = a.get("fusion") or recall_fusion_default(
3480
+ use_fuzzy, use_semantic, use_goal, use_causal)
3350
3481
  # 默认单条上限从 mdcos 取(该模块只在 main() 里惰性导入,模块级没有名字,
3351
3482
  # 直接引用 mdcos.DEFAULT_MAX_ITEM_TOKENS 会 NameError —— 故此处按需导入)。
3352
3483
  from .mdcos import DEFAULT_MAX_ITEM_TOKENS as _DEFAULT_MAX_ITEM
@@ -3448,8 +3579,9 @@ def _dispatch(cg, name, args):
3448
3579
  return {"records": cg.review_records(pid=a.get("pid"))}
3449
3580
 
3450
3581
  if name == "mdcg_forget":
3451
- return cg.forget(a.get("node_id", ""), a.get("reason", ""),
3452
- override=bool(a.get("override")))
3582
+ # 三档自治批次②:与 cg(op=forget) 同一档位路径(forget_gated,见该分支注释)。
3583
+ return cg.forget_gated(a.get("node_id", ""), a.get("reason", ""),
3584
+ override=bool(a.get("override")))
3453
3585
 
3454
3586
  if name == "mdcg_restore":
3455
3587
  return cg.restore(a.get("node_id", ""), force=bool(a.get("force")))
@@ -3555,6 +3687,7 @@ def _start_sustain(cg):
3555
3687
  """启动常驻自维持循环(MDCG_SUSTAIN=0 关闭;间隔可用环境变量调)。"""
3556
3688
  if os.environ.get("MDCG_SUSTAIN", "1") in ("0", "false", "False"):
3557
3689
  return None
3690
+ from . import sleep as _sleep
3558
3691
  from . import sustain
3559
3692
  name = os.environ.get("MDCG_SUSTAIN_NAME") or "md_cg"
3560
3693
  lp = sustain.ensure_loop(
@@ -3563,22 +3696,28 @@ def _start_sustain(cg):
3563
3696
  or sustain.DEFAULT_BEAT_INTERVAL),
3564
3697
  heal_interval=float(os.environ.get("MDCG_SUSTAIN_HEAL")
3565
3698
  or sustain.DEFAULT_HEAL_INTERVAL),
3566
- auto_heal=os.environ.get("MDCG_SUSTAIN_AUTOHEAL", "1")
3567
- not in ("0", "false", "False"),
3699
+ # 四档 auto_* 一律经 sustain 的**单一真源读取器**(P0-2):缺省值只在
3700
+ # `sustain.AUTO_DEFAULTS` 定义一处,此处不得再写字面量(含 env 键名——
3701
+ # 键名真源是 `sustain.AUTO_ENVS`)。整理巡检缺省开:确定性动作、
3702
+ # 永不删除节点(可逆可审计);opt-out `MDCG_AUTO_TIDY=0`。
3703
+ auto_heal=sustain.auto_from_env("auto_heal"),
3568
3704
  scrub_interval=float(os.environ.get("MDCG_SCRUB_INTERVAL")
3569
3705
  or sustain.DEFAULT_SCRUB_INTERVAL),
3570
- auto_scrub=os.environ.get("MDCG_AUTO_SCRUB", "0")
3571
- not in ("0", "false", "False"),
3706
+ auto_scrub=sustain.auto_from_env("auto_scrub"),
3572
3707
  evolve_interval=float(os.environ.get("MDCG_EVOLVE_INTERVAL")
3573
3708
  or sustain.DEFAULT_EVOLVE_INTERVAL),
3574
- auto_evolve=os.environ.get("MDCG_AUTO_EVOLVE", "0")
3575
- not in ("0", "false", "False"),
3709
+ auto_evolve=sustain.auto_from_env("auto_evolve"),
3576
3710
  tidy_interval=float(os.environ.get("MDCG_TIDY_INTERVAL")
3577
3711
  or sustain.DEFAULT_TIDY_INTERVAL),
3578
- # 整理巡检(contextual 同构组聚合+成员降权)默认开:确定性动作、
3579
- # 永不删除节点(可逆可审计);MDCG_AUTO_TIDY=0 关闭
3580
- auto_tidy=os.environ.get("MDCG_AUTO_TIDY", "1")
3581
- not in ("0", "false", "False"))
3712
+ auto_tidy=sustain.auto_from_env("auto_tidy"),
3713
+ # 第六档睡眠周期(§4.7):五个值一律经 `md_cg/sleep.py` 的**单一真源
3714
+ # 读取器**——键名真源是 `sleep.SLEEP_ENV_KEYS`、缺省真源是
3715
+ # `sleep.SLEEP_ENV_DEFAULTS`,本处(与 op 路径)都不得再写字面量。
3716
+ sleep_interval=_sleep.sleep_interval(),
3717
+ auto_sleep=_sleep.sleep_enabled(),
3718
+ sleep_merge=_sleep.sleep_merge_mode(),
3719
+ sleep_window=_sleep.sleep_window(),
3720
+ sleep_scrub_apply=_sleep.sleep_scrub_apply())
3582
3721
  lp.start()
3583
3722
  return lp
3584
3723
 
@@ -4109,6 +4248,32 @@ def main():
4109
4248
  "该进程的代际将无法被外部机械判定)\n")
4110
4249
  except Exception as _exc: # noqa: BLE001 —— 自报不得阻塞启动
4111
4250
  sys.stderr.write("[mdcg-mcp] 自报异常(不阻塞启动): %r\n" % (_exc,))
4251
+ # 代校验(generation 单点,2026-09-30):stdio 服务**真正开始服务之前**,把盘面
4252
+ # 上所有「函数内延迟导入」在本进程的模块面上试解析一遍(导入目标模块 → getattr
4253
+ # 取名字)。根因(第4条取证):长驻进程只在模块首次导入时读盘,而 md_cg 内有
4254
+ # 大量写在函数里的延迟导入(AST 实测 328 处,相当一部分是为避开循环导入)——
4255
+ # 升级后旧代已载入模块与新代惰性模块跨代混用,实测表现为 cg 写入返回
4256
+ # `ImportError: cannot import name 'mint_auto_id' from 'md_cg.mdcg'` 而盘面完全
4257
+ # 正常(该延迟导入要到运行期才炸,且报错点离根因很远)。此处提前到启动期显式
4258
+ # 判一次:**名字缺失即拒绝启动**(fail-closed,消息自带「请重启常驻 MCP 进程」
4259
+ # 指引);外部可选模块缺失(不在盘面的可选降级面,如 whitebox_kb/aeis_core 的
4260
+ # 惰性身体面)非致命,但计数如实上报,不静默。
4261
+ # 调用点必须在**函数体内**:这些延迟导入里相当一部分是为避开循环导入才写在函数
4262
+ # 里的,本校验若放到模块顶层会把避环结构重新咬回来(同族守卫
4263
+ # md_cg/test_generation_guard.py 有 AST 断言钉死这一点)。
4264
+ try:
4265
+ from . import generation as _gen
4266
+ _dep = _gen.verify_delegations()
4267
+ except Exception as _dep_exc: # noqa: BLE001 —— 未通过即拒绝服务,不带病运行
4268
+ sys.stderr.write("[mdcg-mcp] 代校验未通过(拒绝启动,fail-closed):%s\n%s\n"
4269
+ % (type(_dep_exc).__name__, _dep_exc))
4270
+ return 4
4271
+ sys.stderr.write(
4272
+ "[mdcg-mcp] 代校验:延迟导入 %d 项 / 目标模块 %d 个 / 已解析 %d 项 / "
4273
+ "可选缺失 %d 项 · 代 %s\n"
4274
+ % (_dep.get("delegations"), _dep.get("target_modules"),
4275
+ _dep.get("resolved"), len(_dep.get("unavailable") or []),
4276
+ _gen.STARTUP_FINGERPRINT[:12]))
4112
4277
  _start_sustain(cg)
4113
4278
 
4114
4279
  for line in sys.stdin: