@furongjun1999/dsh-memory 0.7.2 → 0.7.4

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 (129) hide show
  1. package/README.md +57 -47
  2. package/codebuddy/CODEBUDDY.md +6 -6
  3. package/docs/README.md +148 -144
  4. package/docs/discipline/harnesses.yaml +16 -8
  5. package/docs/discipline/templates/zcode-user.md.tmpl +49 -0
  6. package/docs/eval/AGI/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_/345/205/255/345/256/266/350/256/260/345/277/206/347/263/273/347/273/237/345/220/214/345/260/272/345/256/236/346/265/213_v3.1.md +340 -0
  7. package/docs/eval/N225_/347/264/242/345/274/225/346/227/245/345/277/227/351/235/236/345/257/271/350/261/241/350/243/205/350/275/275/351/235/242/347/261/273/345/236/213/351/227/270_v1.0.md +2 -2
  8. 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 +1 -1
  9. 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 +1 -1
  10. package/docs/eval/issue43_/351/273/230/350/256/244/347/255/226/347/225/245/344/270/216/351/224/256/347/261/273/345/236/213/351/227/270_v1.1.md +1 -1
  11. package/docs/eval/issue43_/351/273/230/350/256/244/347/255/226/347/225/245/345/212/240/350/275/275/344/270/216/345/207/255/346/215/256/346/230/216/346/226/207/351/230/237/345/210/227_v1.0.md +2 -2
  12. 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 +5 -5
  13. 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 +9 -9
  14. 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 +4 -4
  15. 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 +1 -1
  16. 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 +1 -1
  17. 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 +3 -3
  18. 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 +3 -3
  19. 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 +7 -7
  20. package/docs/eval//344/274/230/345/214/226/347/254/254/344/270/200/346/211/271_/346/216/245/347/272/277/344/270/216/347/255/211/344/273/267/345/217/230/346/215/242_v1.0.md +2 -2
  21. package/docs/eval//344/274/230/345/214/226/347/254/254/344/270/211/346/211/271_/351/227/250/347/246/201/350/275/254/346/255/243/344/270/216/350/260/203/345/272/246/346/255/242/350/241/200/344/270/216/351/227/250/346/216/247/346/224/266/345/217/243_v1.0.md +1 -1
  22. package/docs/eval//344/274/230/345/214/226/347/254/254/344/272/214/346/211/271_/344/276/235/350/265/226/351/200/217/344/274/240/344/270/216/345/257/271/346/213/215/345/217/243/345/276/204/344/270/216/350/264/237/347/274/223/345/255/230_v1.0.md +7 -7
  23. 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 +10 -10
  24. package/docs/eval//345/217/221/345/270/20307_/345/244/226/351/203/250/346/212/245/345/221/212/345/233/233/346/211/271/344/277/256/345/244/215/344/270/216/346/217/222/344/273/266/351/235/242/345/212/240/345/233/272_v1.0.md +4 -0
  25. 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 +1 -1
  26. package/docs/eval//345/217/221/345/270/20311_/345/244/226/346/212/245/345/205/255/350/277/236/344/277/256/344/270/216/344/270/226/347/225/214/346/250/241/345/236/213/345/272/225/345/272/247_v1.0.md +91 -0
  27. package/docs/eval//345/217/221/345/270/20312_/344/270/226/347/225/214/346/250/241/345/236/213/345/212/237/350/203/275/347/253/257_v1.0.md +108 -0
  28. 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 +2 -2
  29. package/docs/eval//347/224/250/346/210/267/350/243/201/345/206/263/345/244/204/347/275/256_/347/254/254/344/270/200/346/211/271_v1.0.md +195 -0
  30. package/docs/eval//347/224/250/346/210/267/350/243/201/345/206/263/345/244/204/347/275/256_/347/254/254/344/272/214/346/211/271B6_/346/234/254/346/234/272/350/267/257/345/276/204/347/233/270/345/257/271/345/214/226_v1.0.md +78 -0
  31. package/docs/eval//347/253/257/345/210/260/347/253/257LoCoMoQA/345/220/214/345/217/243/345/276/204/345/257/271/347/205/247_v1.0.md +1 -1
  32. package/docs/eval//347/253/257/345/210/260/347/253/257/345/271/262/346/211/260/346/261/240/350/257/204/346/265/213_/347/241/256/345/256/232/346/200/247/350/243/201/345/206/263vsLLM_judge_v1.1.md +3 -3
  33. 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 +1 -1
  34. package/docs/eval//347/274/226/347/240/201/351/235/242/345/211/215/347/275/256_/345/205/245/345/217/243/350/207/252/344/277/235/350/257/201UTF8/344/270/216/346/226/207/346/234/254open/345/256/210/345/215/253_v1.0.md +1 -1
  35. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v26.md +196 -0
  36. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v27.md +259 -0
  37. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v28.md +217 -0
  38. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v29.md +270 -0
  39. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v30.md +231 -0
  40. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v31.md +281 -0
  41. package/docs/eval//350/257/255/344/271/211/346/227/266/347/251/272/345/233/276/350/241/245/345/205/250_P1_/346/212/275/345/217/226/345/231/250v4_/344/270/211/347/216/207/350/257/273/346/225/260_v1.0.md +231 -0
  42. package/docs/hive//345/256/211/345/205/250/345/256/241/350/256/241/345/256/236/351/224/232_v0.1.md +1 -1
  43. package/docs/hive//350/234/202/345/267/242M6_ingest/345/256/236/346/226/275/350/256/241/345/210/222_v0.1.md +1 -1
  44. package/docs/hive//350/234/202/345/267/242/345/217/214/345/256/236/344/276/213/344/272/222/351/252/214_/350/256/276/350/256/241/345/256/232/347/250/277.md +1 -1
  45. package/docs/hive//350/234/202/345/267/242/350/256/276/350/256/241_/347/220/206/350/256/272/345/257/271/351/275/220_v0.1.md +17 -17
  46. package/docs/hive//350/234/202/345/267/242/350/277/255/344/273/243_/345/256/217/350/247/202/344/270/216/347/276/244/344/275/223/350/260/203/345/272/246_v0.1.md +1 -1
  47. package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +2 -2
  48. package/docs/mdcg//344/270/226/347/225/214/346/250/241/345/236/213/345/212/237/350/203/275/347/253/257_/344/275/277/347/224/250/344/270/216/350/277/220/347/273/264_v1.0.md +305 -0
  49. 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 +45 -42
  50. package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +29 -8
  51. 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 +71 -1
  52. package/docs/plans//345/205/250/344/270/255/346/226/207/347/274/226/347/240/201/344/270/216/350/234/202/345/267/242/344/273/273/345/212/241/346/240/207/350/257/206/345/245/221/347/272/246_v2.0.md +1 -1
  53. package/docs/plans//345/244/232/344/270/273/344/275/223/344/270/226/347/225/214/346/250/241/345/236/213_/345/257/271/351/275/220/350/257/204/344/274/260/344/270/216/350/220/275/345/234/260/350/256/276/350/256/241_v0.1.md +141 -0
  54. 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 +2 -2
  55. package/docs/plans//350/257/255/344/271/211/346/227/266/347/251/272/345/233/276/350/241/245/345/205/250_/344/270/226/347/225/214/346/250/241/345/236/213/345/212/237/350/203/275/347/253/257_/350/256/276/350/256/241_v0.1.md +107 -0
  56. 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 +6 -5
  57. package/docs//347/247/244_/350/256/260/345/277/206/347/263/273/347/273/237/350/257/204/346/265/213/350/247/204/350/214/203_v2.1.md +225 -0
  58. package/dsh/cordis-patch-profile-web.example.yml +2 -2
  59. package/lib/bridge.js +9 -0
  60. package/lib/hooks.d.ts +8 -1
  61. package/lib/hooks.js +134 -50
  62. package/lib/lib/hook_audit.d.ts +55 -0
  63. package/lib/lib/hook_audit.js +208 -0
  64. package/lib/lib/mutual.js +5 -1
  65. package/lib/lib/roleplay_web.js +17 -2
  66. package/lib/lib/session_state.d.ts +56 -0
  67. package/lib/lib/session_state.js +124 -0
  68. package/lib/tools.js +12 -1
  69. package/md_cg/auditview.py +248 -0
  70. package/md_cg/bench_e2e_judge.py +4 -2
  71. package/md_cg/branches.py +29 -3
  72. package/md_cg/consistency.py +38 -13
  73. package/md_cg/datapath.py +38 -11
  74. package/md_cg/forgetting.py +15 -2
  75. package/md_cg/freshness.py +32 -11
  76. package/md_cg/ghostref.py +130 -0
  77. package/md_cg/judgment_manifest.py +7 -0
  78. package/md_cg/lifecycle.py +38 -0
  79. package/md_cg/mcp_server.py +122 -12
  80. package/md_cg/mdcg.py +4923 -4822
  81. package/md_cg/mdcos.py +109 -21
  82. package/md_cg/nodefile.py +77 -13
  83. package/md_cg/readcache.py +29 -4
  84. package/md_cg/selfreport.py +12 -3
  85. package/md_cg/sleep.py +129 -13
  86. package/md_cg/sources.py +1 -1
  87. package/md_cg/state_events.py +131 -0
  88. package/md_cg/state_slots.py +221 -0
  89. package/md_cg/stg.py +68 -3
  90. package/md_cg/sustain.py +1436 -1436
  91. package/md_cg/test_auditview.py +277 -0
  92. package/md_cg/test_autonomy_modes.py +2 -1
  93. package/md_cg/test_boot_openblas_guard.py +197 -0
  94. package/md_cg/test_ccg_form_parity.py +95 -4
  95. package/md_cg/test_cons200_scan_selection.py +4 -2
  96. package/md_cg/test_datapath_device_name.py +62 -3
  97. package/md_cg/test_generation_guard.py +5 -2
  98. package/md_cg/test_ghostref.py +182 -0
  99. package/md_cg/test_interop.py +1 -1
  100. package/md_cg/test_issue52_scan_condition_first.py +1 -1
  101. package/md_cg/test_lifecycle_retire_leak.py +408 -0
  102. package/md_cg/test_lock.py +16 -7
  103. package/md_cg/test_m3_h9_semantic_guard.py +1 -1
  104. package/md_cg/test_n230_dirty_replay.py +375 -375
  105. package/md_cg/test_n238_body_text_parity.py +156 -0
  106. package/md_cg/test_n276_probe_hoist.py +199 -0
  107. package/md_cg/test_p4_freshness.py +95 -1
  108. package/md_cg/test_policy_required_ccg.py +3 -1
  109. package/md_cg/test_read_face_semantics.py +6 -3
  110. package/md_cg/test_readcache_prodpath.py +66 -0
  111. package/md_cg/test_sleep.py +58 -1
  112. package/md_cg/test_sleep_p1.py +218 -2
  113. package/md_cg/test_state_event_op.py +514 -0
  114. package/md_cg/test_state_events.py +178 -0
  115. package/md_cg/test_state_slots.py +691 -0
  116. package/md_cg/test_time_core_lint.py +8 -1
  117. package/md_cg/test_writepipe.py +8 -5
  118. package/md_cg/tokens.py +18 -2
  119. package/md_cg/writepipe.py +76 -0
  120. package/package.json +2 -2
  121. package/skills/plugin.json +1 -1
  122. package/src/bridge.ts +9 -0
  123. package/src/hooks.ts +599 -517
  124. package/src/lib/hook_audit.ts +243 -0
  125. package/src/lib/mutual.ts +5 -1
  126. package/src/lib/roleplay_web.ts +17 -2
  127. package/src/lib/session_state.ts +127 -0
  128. package/src/tools.ts +12 -1
  129. package/zcode/AGENTS.md +6 -6
package/md_cg/datapath.py CHANGED
@@ -50,22 +50,49 @@ ENV_AUX_ROOT = "MDCG_AUX_ROOT"
50
50
  DEFAULT_AUX_DIRNAME = ".mdcg"
51
51
 
52
52
 
53
+ #: Windows 保留设备名全集(末段直判用;比对前统一 .upper(),故此处全大写):
54
+ #: DOS 设备名 CON/PRN/AUX/NUL、控制台伪设备 CONIN$/CONOUT$、COM1-9/LPT1-9,
55
+ #: 以及 Windows 承认的上标数字变体 COM¹²³/LPT¹²³(上标 0 与 4-9 不是保留名)。
56
+ _RESERVED_DEVICE_NAMES = frozenset(
57
+ ["CON", "PRN", "AUX", "NUL", "CONIN$", "CONOUT$"]
58
+ + [f"COM{i}" for i in range(1, 10)]
59
+ + [f"LPT{i}" for i in range(1, 10)]
60
+ + ["COM\u00b9", "COM\u00b2", "COM\u00b3",
61
+ "LPT\u00b9", "LPT\u00b2", "LPT\u00b3"])
62
+
63
+
64
+ # 生效条件:纯字符串判定(不触盘、无平台分支——平台闸在调用处 _abs_host_path);
65
+ # 传入 path 归一("/" → "\")后去尾随反斜杠取末段,末段按首个 "." 截断取 stem、
66
+ # 再去尾随空格,stem.upper() 命中 _RESERVED_DEVICE_NAMES 即返回 True,否则 False。
67
+ # 末段直判不依赖 OS/Python 版本的 GetFullPathNameW 吞并行为——较新 Windows 构建
68
+ # 不再把「目录\aux」吞成 \\.\aux(issue #57),前缀判据随之失效,本函数是稳定判据。
69
+ def _reserved_device_tail(path: str) -> bool:
70
+ seg = path.replace("/", "\\").rstrip("\\").split("\\")[-1]
71
+ stem = seg.split(".", 1)[0].rstrip(" ")
72
+ return stem.upper() in _RESERVED_DEVICE_NAMES
73
+
74
+
53
75
  # 生效条件:p 按仓库既有口径 expanduser+abspath 归一;平台为 Windows(os.name=="nt")
54
- # 且归一结果以设备命名空间前缀("\\\\.\\")开头时抛 ValueError——保留设备名末段
55
- # (aux/con/nul/prn/com1-9/lpt1-9 等)会被 GetFullPathNameW 吞成设备路径
56
- # (例 D:\sandbox\aux → \\.\aux),目录语义静默丢失,密钥/令牌/数据覆盖键随之
57
- # 静默失联;非 Windows 平台该形态是合法目录名字面量,不判定。归一是纯字符串
58
- # 操作不触盘,路径无需存在即可复现。env_key 传覆盖键名(env 变量名或 paths.json
59
- # 键),仅用于错误消息定位误配来源;为空时消息以「该路径」指代。
76
+ # 且命中两因之一时抛 ValueError——① 归一结果以设备命名空间前缀("\\\\.\\")开头:
77
+ # 保留设备名末段(aux/con/nul/prn/com1-9/lpt1-9 等)被 GetFullPathNameW 吞成设备
78
+ # 路径(例 D:\sandbox\aux → \\.\aux);② 归一结果末段直判为保留设备名
79
+ # (_reserved_device_tail):较新 Windows 构建不再吞并(issue #57),旧前缀判据
80
+ # 失效,末段直判不依赖 OS 行为——两因并置互为兜底。后果同:目录语义静默丢失,
81
+ # 密钥/令牌/数据覆盖键随之静默失联;非 Windows 平台该形态是合法目录名字面量,
82
+ # 不判定。归一是纯字符串操作不触盘,路径无需存在即可复现。env_key 传覆盖键名
83
+ # (env 变量名或 paths.json 键),仅用于错误消息定位误配来源;为空时消息以
84
+ # 「该路径」指代。
60
85
  def _abs_host_path(p: str, env_key: str = "") -> str:
61
86
  r = os.path.abspath(os.path.expanduser(p))
62
- if os.name == "nt" and r.startswith("\\\\.\\"):
87
+ if os.name == "nt" and (r.startswith("\\\\.\\")
88
+ or _reserved_device_tail(r)):
63
89
  who = env_key or "该路径"
64
90
  raise ValueError(
65
- f"{who} 归一后解析为 Windows 设备命名空间路径 {r!r}:末段是 Windows "
66
- "保留设备名(aux/con/nul/prn/com1-9/lpt1-9 等),GetFullPathNameW 会把"
67
- "整个目录吞成设备路径,目录语义静默丢失(落在此处的密钥/令牌/数据会"
68
- f"静默失联);请把 {who} 改指向末段不含保留设备名的普通目录。")
91
+ f"{who} 归一后末段是 Windows 保留设备名({r!r}):GetFullPathNameW "
92
+ "可能把整个目录吞成设备路径(\\\\.\\aux 形态,目录语义静默丢失);"
93
+ "较新 Windows 构建即便不吞并,保留设备名末段也一律按误配拒绝——"
94
+ "本守卫末段直判不依赖 OS 吞并行为(issue #57)。请把 "
95
+ f"{who} 改指向末段不含保留设备名的普通目录。")
69
96
  return r
70
97
 
71
98
 
@@ -174,7 +174,7 @@ def self_coverage(cg, node_id, content):
174
174
  return round(_coverage(bigrams(payload(content)), body), 4)
175
175
 
176
176
 
177
- # 生效条件:content 经 payload/bigrams 得空集合时直接返回零值 best(max=0.0、with=None、compared=0);否则遍历 cg.index 的 nodes,跳过 nid==exclude,layer 为真值时只比较 str(layer 字段 or "")==layer 的节点,cg.get(nid) 抛异常/返回假值、或该节点 content 的 bigrams 为空则跳过,每计入一个节点后若 n>=limit 立即 break(故 limit 为 0 或负数时只比较首项即停),返回覆盖度最大者 best(无覆盖度提升时不更新 with/jaccard,compared 为实际计入数)。
177
+ # 生效条件:content 经 payload/bigrams 得空集合时直接返回零值 best(max=0.0、with=None、compared=0);否则**循环外经 cg._maybe_reload_index() 单探一次**(N276:无该方法/不可调用的载体跳过,环内回落原 cg.get(nid)),遍历 cg.index 的 nodes,跳过 nid==exclude,layer 为真值时只比较 str(layer 字段 or "")==layer 的节点,cg.get(nid, probe=False)(无探活面的载体回落 cg.get(nid))抛异常/返回假值、或该节点 content 的 bigrams 为空则跳过,每计入一个节点后若 n>=limit 立即 break(故 limit 为 0 或负数时只比较首项即停),返回覆盖度最大者 best(无覆盖度提升时不更新 with/jaccard,compared 为实际计入数)。
178
178
  def redundancy(cg, content, layer="contextual", exclude=None, limit=MAX_COMPARE):
179
179
  """Q1 重复?——新内容被既有同层节点覆盖的最大比例。"""
180
180
  new = bigrams(payload(content))
@@ -182,6 +182,18 @@ def redundancy(cg, content, layer="contextual", exclude=None, limit=MAX_COMPARE)
182
182
  if not new:
183
183
  return best
184
184
  nodes = ((getattr(cg, "index", None) or {}).get("nodes") or {})
185
+ # N276(第 32 轮性能面):**批内代际探针只做一次**——循环里每次 cg.get
186
+ # 都会经 MdCG.get 首行重算索引签名(快照 stat + listdir + 逐分片 stat,
187
+ # 单点实测 ~38µs;本库 240 次/调用全同签名重探)。此处循环外探一次、
188
+ # 环内 cg.get(nid, probe=False)。语义窗口=**本调用期间「他进程写入」
189
+ # 不被探知**(毫秒级;读取环本就按当次快照判定,与读缓存/热缓存已声明
190
+ # 的进程内一致性边界同款,故注释记账即可);无探活面的载体(测试桩)
191
+ # 回落原 cg.get(nid),行为零变。
192
+ _probe = getattr(cg, "_maybe_reload_index", None)
193
+ if callable(_probe):
194
+ _probe()
195
+ else:
196
+ _probe = None
185
197
  n = 0
186
198
  for nid in list(nodes.keys()):
187
199
  if nid == exclude:
@@ -189,7 +201,8 @@ def redundancy(cg, content, layer="contextual", exclude=None, limit=MAX_COMPARE)
189
201
  if layer and str(nodes[nid].get("layer") or "") != layer:
190
202
  continue
191
203
  try:
192
- node = cg.get(nid)
204
+ node = (cg.get(nid, probe=False) if _probe is not None
205
+ else cg.get(nid))
193
206
  except Exception:
194
207
  node = None
195
208
  if not node:
@@ -179,22 +179,31 @@ def resolve_gamma(gamma=None) -> float:
179
179
  # 核(一律经 time_core;本模块不写指数核)
180
180
  # --------------------------------------------------------------------------
181
181
 
182
- # 生效条件:gamma 与 dt_days(单位=天)任意可转 float 时,返回 time_core.cred_factor(gamma, dt_days, floor, ceil);dt 非有限值(inf/nan)时同样交给 cred_factor(inf → 取 floor);
182
+ # 生效条件:gamma 与 dt_days(单位=天)任意可转 float 时,返回 time_core.cred_factor(gamma, dt_days, floor, ceil);dt 非有限值(inf/nan)时同样交给 cred_factor(inf → 取 floor);enabled 为 None(缺省)时自算(模块级 enabled()),显式传 bool 时按传入值——两者皆假值即恒返回 1.0;
183
183
  def decay_factor(gamma: float, dt_days: float, floor: float = SCORE_FLOOR,
184
- ceil: float = SCORE_CEIL) -> float:
184
+ ceil: float = SCORE_CEIL, enabled=None) -> float:
185
185
  """时间衰减乘子 `e^(-γ·Δt)`(**核来自唯一权威** `time_core.cred_factor`)。
186
186
 
187
187
  ⚠ `dt_days` 的单位**必须**是「天」(`DT_UNIT`);调用方负责换算,
188
188
  换算单点是 `entry_weight`(`(now - created_at) / SECONDS_PER_DAY`)。
189
+
190
+ `enabled`(N274):None=自算(默认,行为与改动前逐位一致);显式传
191
+ bool 时不再自算——供 `entry_weight`/`_score` 把开关**循环外解析一次**
192
+ 后透传,消掉逐节点 env 读取。自算分支写 `globals()["enabled"]()` 而非
193
+ 直接 `enabled()`:形参同名会遮蔽模块函数,而 `globals()` 的解析语义与
194
+ 改前那句 `enabled()` 逐位一致(含测试对模块函数的打桩/替换面)。
189
195
  """
190
- if not enabled():
196
+ if enabled is None:
197
+ enabled = globals()["enabled"]()
198
+ if not enabled:
191
199
  return 1.0
192
200
  return _tc.cred_factor(float(gamma), float(dt_days), float(floor),
193
201
  float(ceil))
194
202
 
195
203
 
196
- # 生效条件:access_count 与 last_access 任意(不可转数值按 0 处理)、importance 任意可转 float;按 REFRESH_PARAMS 的三档判定返回 1.0+gain / 1.0−gain / 1.0;
197
- def refresh_factor(access_count=0, last_access=0, importance=0.0) -> float:
204
+ # 生效条件:access_count 与 last_access 任意(不可转数值按 0 处理)、importance 任意可转 float;enabled 为 None(缺省)时自算(模块级 enabled()),显式传 bool 时按传入值——两者皆假值即恒返回 1.0;否则按 REFRESH_PARAMS 的三档判定返回 1.0+gain / 1.0−gain / 1.0;
205
+ def refresh_factor(access_count=0, last_access=0, importance=0.0,
206
+ enabled=None) -> float:
198
207
  """访问刷新/降权乘子(**门槛与步长照抄 AEIS** `consolidate_cycle`)。
199
208
 
200
209
  判定序(与 AEIS 同序同门):
@@ -209,8 +218,13 @@ def refresh_factor(access_count=0, last_access=0, importance=0.0) -> float:
209
218
  在**从未被访问**(`access_count == 0`)时也为真——照抄原样会让「刚写、
210
219
  从未调用的高重要度节点」白拿一次刷新。加此闸后「未被调用」只能不刷新,
211
220
  不会反被奖励(语义仍与 §7.1「被经常调用的记忆其权重被刷新」一致)。
221
+
222
+ `enabled`(N274):None=自算(默认,行为与改动前逐位一致);显式传
223
+ bool 时不再自算——与 `decay_factor` 同一透传口径(见其 docstring)。
212
224
  """
213
- if not enabled():
225
+ if enabled is None:
226
+ enabled = globals()["enabled"]()
227
+ if not enabled:
214
228
  return 1.0
215
229
  p = REFRESH_PARAMS
216
230
  try:
@@ -251,8 +265,8 @@ def protected_floor(entry) -> float:
251
265
  return max(SCORE_FLOOR, min(1.0, IMPORTANCE_PROTECT / imp))
252
266
 
253
267
 
254
- # 生效条件:entry 为可取 created_at / access_count / last_access / importance / protected 的映射;fm 为真值时上述五个键优先取自 fm(缺键回落 entry);now 为假值(None)时取 freshness_now();无条件返回 (乘子, 依据 dict)——乘子 = decay_factor(gamma, |now-created_at|/86400, protected_floor(取数后的映射), SCORE_CEIL) × refresh_factor(...);created_at 缺失/不可转数值/≤0 时 dt_days 取 inf(判「距参照无穷远」,由 floor 兜住);
255
- def entry_weight(entry, now=None, gamma=None, fm=None) -> tuple:
268
+ # 生效条件:entry 为可取 created_at / access_count / last_access / importance / protected 的映射;fm 为真值时上述五个键优先取自 fm(缺键回落 entry);now 为假值(None)时取 freshness_now();enabled 为 None(缺省)时自算(模块级 enabled()),显式传 bool 时按传入值——两者皆假值即早退返回 (1.0, {"disabled": True})(早退先于 now/γ 解析,与改前同序);否则无条件返回 (乘子, 依据 dict)——乘子 = decay_factor(gamma, |now-created_at|/86400, protected_floor(取数后的映射), SCORE_CEIL, enabled=…) × refresh_factor(..., enabled=…);created_at 缺失/不可转数值/≤0 时 dt_days 取 inf(判「距参照无穷远」,由 floor 兜住);
269
+ def entry_weight(entry, now=None, gamma=None, fm=None, enabled=None) -> tuple:
256
270
  """索引条目 → 分数乘子 `(乘子, 依据)`(**检索期唯一取数点**,免读文件)。
257
271
 
258
272
  `entry` 直接来自索引快照(`_node_entry` 已落 `created_at` /
@@ -260,6 +274,11 @@ def entry_weight(entry, now=None, gamma=None, fm=None) -> tuple:
260
274
 
261
275
  `fm` 为可选覆盖源(写面刚落的 frontmatter 可能比索引条目新):只为
262
276
  避免在热路径上拷贝 dict 而设,取数优先级 `fm > entry > 缺省`。
277
+
278
+ `enabled`(N274):None=自算(默认,行为与改动前逐位一致);显式传
279
+ bool 时不再自算,且向下透传给 `decay_factor`/`refresh_factor`——它们
280
+ 同样不再各自读 env。自算分支经 `globals()["enabled"]()`(形参遮蔽,
281
+ 解析语义同改前的 `enabled()`,见 `decay_factor` 同注)。
263
282
  """
264
283
  e = entry or {}
265
284
  keys = ("created_at", "access_count", "last_access", "importance",
@@ -269,7 +288,9 @@ def entry_weight(entry, now=None, gamma=None, fm=None) -> tuple:
269
288
  for k in keys}
270
289
  else:
271
290
  src = {k: e.get(k) for k in keys}
272
- if not enabled():
291
+ if enabled is None:
292
+ enabled = globals()["enabled"]()
293
+ if not enabled:
273
294
  return 1.0, {"disabled": True}
274
295
  g = resolve_gamma(gamma)
275
296
  ref = freshness_now() if now is None else float(now)
@@ -286,9 +307,9 @@ def entry_weight(entry, now=None, gamma=None, fm=None) -> tuple:
286
307
  # 微变)且两侧跨语言同值。详见 DT_QUANTUM_DAYS 的说明。
287
308
  dt_days = float(int(dt_days / DT_QUANTUM_DAYS)) * DT_QUANTUM_DAYS
288
309
  floor = protected_floor(src)
289
- d = decay_factor(g, dt_days, floor, SCORE_CEIL)
310
+ d = decay_factor(g, dt_days, floor, SCORE_CEIL, enabled=enabled)
290
311
  r = refresh_factor(src.get("access_count"), src.get("last_access"),
291
- src.get("importance"))
312
+ src.get("importance"), enabled=enabled)
292
313
  return d * r, {"gamma": g, "dt_days": dt_days, "unit": DT_UNIT,
293
314
  "decay": d, "refresh": r, "floor": floor,
294
315
  "protected": bool(src.get("protected"))}
@@ -0,0 +1,130 @@
1
+ # -*- coding: utf-8 -*-
2
+ """幽灵引用检查器(A2 落地):短语层标记 + id 层降级告警——**只标记、不拒收**。
3
+
4
+ 背景(多主体世界模型对齐 v0.1 §2 L1「转引不得升级」):CD-WHALE-01(鲸娘语料
5
+ 731 轮)发现 C 的现实形态是**自然语言回指**(「上次的香薰蜡烛」「明明讲过的那
6
+ 件事」)——被引对象在库里没有任何记录,引用悬空且无任何防线(探针读数:
7
+ 短语层零痕迹、id 层「目标晚于自身」零告警)。本模块补两条**标记级**防线:
8
+
9
+ ① 短语层:正文以回指短语(「上次的」「之前说的」「明明讲过」)指代一个
10
+ 在句内**无可解析出处**(句内无库内 id)的对象时,产出幽灵引用短语清单
11
+ ——写入链把它落 `fm.uncertain_refs` 并在出口告警;**不拒收**(回指是
12
+ 现实写作的常态,拒收会把正常记忆挡在门外;标记让「不确定」可被后续
13
+ 裁决与检索看见)。
14
+ ② id 层:正文引用了一个库内目标,但本文档声明的生效起点(valid_from /
15
+ effective_from)**早于**目标创建时刻——「引用了当时尚不存在的目标」。
16
+ 产出 late_refs 告警清单;同样**不改建边判定、不拒收**。
17
+
18
+ 判据边界(如实):
19
+ · 句级判定:短语所在**句子**([。!?;\n] 为界)内无库内 id ⇒ 幽灵;
20
+ 跨句引用(一句短语、邻句 id)不消解标记——保守方向是「宁标勿漏」。
21
+ · 仅写链(MCP op=write)路径生效(与 linkref 同界);库层直调 cg.add
22
+ 不经检测。
23
+ · 短语表保守(明确的时间/人称回指词),**不含**「那条/那根」这类泛指示词
24
+ (误报率高,标记面宁可窄);误报后果 = fm 多一个标记。
25
+ · 检测面**不查源节点 id 形态**(不同于 linkref 的 is_linkable_source
26
+ 门槛):标记是**内容属性**——无论节点 id 是否合规,正文里的无出处回指
27
+ 都值得标记;建边是**图属性**,才需要形态门槛(防图污染)。两者有意
28
+ 不同界。**late 面例外**:它消费 linkref 解析出的 targets,源 id 不合
29
+ 形态 ⇒ 无 targets ⇒ late 静默不查(与 linkref 同界,2026-10-05 复核
30
+ info-④ 登记)。
31
+ """
32
+ from __future__ import annotations
33
+
34
+ import re
35
+
36
+ __all__ = ["PHRASES", "SENTENCE_SPLIT_RE", "find_ghost_phrases", "late_targets",
37
+ "check"]
38
+
39
+ #: 回指短语表(时间回指 + 人称回指;CD-WHALE-01 发现 C 的词形采样,
40
+ #: 对齐评估 v0.1 §2 L1 的「上次的/之前的/明明讲过」三例)。
41
+ PHRASES = (
42
+ "上次的", "上次说", "上次买", "上次那", "上次提",
43
+ "之前的", "之前说", "之前买", "之前那", "之前提",
44
+ "明明讲过", "明明说过", "明明说", "明明提",
45
+ "刚才说", "刚才的", "前述的", "先前说", "早先的",
46
+ "你说的", "我说过的", "你提过的",
47
+ )
48
+
49
+ #: 句界(句级判定的切分面)。保留句子文本、丢弃界符。
50
+ SENTENCE_SPLIT_RE = re.compile(r"[。!?;\n]+")
51
+
52
+
53
+ def find_ghost_phrases(text, known=None) -> list:
54
+ """短语层检测:返回**无可解析出处**的回指短语列表(去重保序)。
55
+
56
+ known —— 库内节点 id 集合(白名单)。句内含形态合法**且在册**的 id 即
57
+ 视为「有出处」(该句非幽灵);传 None 时降级为只判形态(保守少报)。
58
+ """
59
+ if not text:
60
+ return []
61
+ from . import linkref
62
+ out = []
63
+ seen = set()
64
+ for sent in SENTENCE_SPLIT_RE.split(str(text)):
65
+ hits = [p for p in PHRASES if p in sent]
66
+ if not hits:
67
+ continue
68
+ # 句内子串去重(最长优先):「明明说过」同时命中「明明说过」与
69
+ # 「明明说」——同一现象的一个更长粒度标记即可(复核 info-2:
70
+ # 冗余双标记挤占 cap 16)。跨句不去重(不同句各自独立)。
71
+ hits = [p for p in hits
72
+ if not any(p != q and p in q for q in hits)]
73
+ # 句内是否有可解析出处:id 形态命中 + (调用方给了 known 时)在册。
74
+ refs = linkref.extract_refs(sent, known=known)
75
+ if refs:
76
+ continue
77
+ for p in hits:
78
+ if p not in seen:
79
+ seen.add(p)
80
+ out.append(p)
81
+ return out
82
+
83
+
84
+ def late_targets(cg, targets, own_from) -> list:
85
+ """id 层检测:目标创建时刻晚于本文档声明的生效起点 ⇒「目标晚于自身」。
86
+
87
+ targets —— linkref 解析出的引用目标 id 列表(ctx["linkref_targets"])。
88
+ own_from —— 本文档声明的生效起点(valid_from / effective_from);缺省
89
+ None 表示未声明时间轴,此时不做检查(写入 created_at=now,
90
+ 「目标晚于自身」结构上不可能)。
91
+
92
+ 返回 [{target, target_created, own_from}](cap 10,保序)。
93
+ """
94
+ if own_from is None or not targets:
95
+ return []
96
+ try:
97
+ own = float(own_from)
98
+ except (TypeError, ValueError):
99
+ return []
100
+ nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
101
+ out = []
102
+ for tid in targets:
103
+ tid = str(tid)
104
+ e = nodes.get(tid) or {}
105
+ tc = e.get("created_at")
106
+ if tc is None:
107
+ rec = cg.get(tid) or {}
108
+ tc = ((rec.get("frontmatter")) or {}).get("created_at")
109
+ try:
110
+ tc = float(tc)
111
+ except (TypeError, ValueError):
112
+ continue
113
+ if tc > own:
114
+ out.append({"target": tid, "target_created": tc, "own_from": own})
115
+ if len(out) >= 10:
116
+ break
117
+ return out
118
+
119
+
120
+ # 生效条件:总是返回 {"ghost_phrases": [...], "late_refs": [...]} 两键 dict(各可为空列表);
121
+ # 内部对 text 做短语层检测(known 取 cg 索引键集合),对 ctx 已解析的 linkref 目标做 id 层检测;
122
+ # 不做任何落盘/短路(纯读)。
123
+ def check(cg, text, linkref_targets=None, own_from=None) -> dict:
124
+ """组合检测(纯读,无副作用):供写入链闸调用。"""
125
+ nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
126
+ known = set(nodes.keys())
127
+ return {
128
+ "ghost_phrases": find_ghost_phrases(text, known=known),
129
+ "late_refs": late_targets(cg, linkref_targets or [], own_from),
130
+ }
@@ -33,6 +33,7 @@ hive serve 心跳)用的是 `scripts/judgment_manifest.py`,两侧 PATTERNS
33
33
  md_cg/test_*.py Python 侧测试套件
34
34
  compiler/tests/*.py compiler 测试
35
35
  swarm/tests/*.py swarm 测试
36
+ test/{hive_exec,hive_wm}_test.py test/ 侧逐文件裁决可收件(A6;精确名冻结)
36
37
 
37
38
  安装态(出货包)只有 `md_cg/test_*.py` 一组可得——其余组命中数为 0,
38
39
  `missing_patterns` 显式随清单与冻结凭证落盘(不静默少算);A3 两侧必须在
@@ -55,6 +56,12 @@ PATTERNS = [
55
56
  ("md_cg", "test_*.py"),
56
57
  ("compiler/tests", "*.py"),
57
58
  ("swarm/tests", "*.py"),
59
+ # test/(A6,2026-10-05):与 scripts/ 版逐字同步——test/ 侧逐文件裁决可收件的
60
+ # 精确名冻结(未过裁决的件不进判据面)。批次99 补收:chaos 套件的陈旧登记已
61
+ # 同步(FI-M04 读码代理断言改 AST 语义判据 + 去锁定点变异自证)。
62
+ ("test", "hive_exec_test.py"),
63
+ ("test", "hive_wm_test.py"),
64
+ ("test/chaos_injection", "run_all.py"),
58
65
  ]
59
66
 
60
67
 
@@ -103,6 +103,44 @@ def state_of(fm) -> str:
103
103
  return s if s in STATES else "active"
104
104
 
105
105
 
106
+ # 生效条件:入参 e 为索引条目(或同形 frontmatter 副本)时返回 state_of(e) == "archived" 的布尔结果;缺 STATE_FIELD 或取值不在 STATES 时 state_of 回落 "active" ⇒ 返回 False。
107
+ def is_archived(e) -> bool:
108
+ """默认检索剔除面=本函数(判据单点)。
109
+
110
+ `archived` 的语义(模块 docstring :42)是「不再参与**默认检索**」——本函数是该
111
+ 宣称的**唯一判据**,全部消费点共同引用(判据单点,多个消费点):
112
+
113
+ · cg 检索面:`MdCGOS._candidates`(mdcos.py)——`MdCGSecure._candidates` 经
114
+ `super()` 覆盖叠加读可见性,故全部**检索类** cg 读 op(search / search_rrf /
115
+ recall / route…)都经它;
116
+ · stg 扫描面:`stg._scan_one`(stg.py,`_scan` 的逐条实现单点)——timeline /
117
+ anchors / consistency 三 op 的候选面同点覆盖(`relation` 面是显式 id 直读,
118
+ `state_chain` 走 append-only 台账,两者不经扫描面);
119
+ · 会话续接注入面:`MdCGOS.session_recall`(mdcos.py)的四个取数段——会话要点
120
+ `_session_notes`、活跃目标 `goals`、任务台账 `tasks`、未解问题 `unresolved`
121
+ (`goals`/`tasks` 经 `_retired_by_id` 按 id 回查索引后交本判据)。该 op 是
122
+ 「装配续接包注入上下文/回给调用方」的注入面,与检索面同属「退役即不再默认
123
+ 回出」;它自持取数面(不经 `_candidates`),故单点接线(独立复核 2026-10-06
124
+ 两轮实测:接线前 archived 要点摘要、未解问题正文、**活跃目标与任务卡**均经
125
+ 该 op 照旧回出;`recent` 段是事件窗口、`self_state` 是只读快照卡,不含节点
126
+ 条目,不涉退役)。
127
+
128
+ **fail-open**(缺键/非法值 → active):存量节点不回填也照常参与检索
129
+ (存量零迁移、不误剔);「不误剔」的另一半是**只剔 archived**——
130
+ converged / demoted 是**降权轴**,仍参与默认检索。
131
+
132
+ 不适用条件(**不**消费本函数,故 archived 期间照常可达):显式 id 直读
133
+ (`cg.get`)、审计面(`op=audit`)、工程台账的**显式管理查询面**
134
+ (`cg(op=goal, action=list)` / `cg(op=task, action=list)`;注意与消费面之别——
135
+ 会话续接注入包内的对应段已按上表剔除,两者不冲突)、负记忆层
136
+ (rejected / unresolved 的覆盖标记面)、统计面(health / whoami / conformance)
137
+ ——「退役不删除、可显式恢复」的承诺要求它们在 archived 期间照常可达。
138
+ 边界(已知未覆盖面,另立裁定):基类 `MdCG.search`、`chain.adjacency` 拓扑面、
139
+ `subgraph.children_index/parents_index` 结构面、rust 检索内核。
140
+ """
141
+ return state_of(e) == "archived"
142
+
143
+
106
144
  # 生效条件:按 _RANK.get(dst, 0) > _RANK.get(src, 0) 判定,src 或 dst 不在 _RANK 键中时该侧按 0 参与比较。
107
145
  def is_downgrade(src: str, dst: str) -> bool:
108
146
  """是否向「更低」的状态迁移(active < converged < demoted < archived)。"""