@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
@@ -19,6 +19,14 @@ paths.json 直值面)改走该辅助。非 Windows 平台 ``\\\\.\\aux`` 是
19
19
  承接面:``mcp_server.main()`` 启动早期对 root/aux 各探一次,ValueError →
20
20
  stderr 告警 + return 2(对照 `_md_cg_` 前缀守卫先例「启动即拒绝,不带病运行」)。
21
21
 
22
+ issue #57 加固(判据与 OS 行为解耦):较新 Windows 构建的 GetFullPathNameW 不再把
23
+ 「<目录>\\aux」吞成 ``\\.\aux``,前缀判据随之失效(报告人红单:aux/con/com1/lpt1
24
+ 漏判、nul 未漏,本机旧构建仍吞、复现不了该红)——``md_cg/datapath.py`` 新增
25
+ ``_RESERVED_DEVICE_NAMES`` 与纯函数 ``_reserved_device_tail``(末段直判),
26
+ ``_abs_host_path`` 条件改为「设备前缀判据 或 末段直判」两因并置。§7 谓词表直调
27
+ 纯函数(平台无关闭集);§8 把模块所用 ``os.path.abspath`` 临时换成恒等函数、模拟
28
+ 报告人系统复现漏判现场——删掉「或末段直判」后 §8 在 Windows 上必转红(变异自证锚点)。
29
+
22
30
  隔离纪律:四个覆盖键(MDCG_STATE_ROOT/MDCG_DATA_ROOT/MDCG_ROOT/MDCG_AUX_ROOT)
23
31
  与 DSH_HOME 以 os.environ 副本注入、finally 逐键还原;探针路径全部是
24
32
  tempfile.mkdtemp 下**不存在**的纯字符串(或正常临时子目录),不创建任何含
@@ -40,10 +48,12 @@ except AttributeError:
40
48
  pass
41
49
 
42
50
  from .datapath import (ENV_AUX_ROOT, ENV_DATA_ROOT, ENV_MDCG_ROOT,
43
- ENV_STATE_ROOT, aux_root, data_root, mdcg_root,
44
- state_root)
51
+ ENV_STATE_ROOT, _reserved_device_tail, aux_root,
52
+ data_root, mdcg_root, state_root)
45
53
 
46
- FLOOR = 8 # 断言总数下限(防假绿)
54
+ # 断言总数下限(防假绿):含 [7] 谓词表 35 条 + [8] 现场模拟 2 条,
55
+ # 按新增断言数上调后仍留 ≥8 条余量。
56
+ FLOOR = 50
47
57
  PASS, FAIL = 0, 0
48
58
 
49
59
  #: 守卫注入/还原面(含 DSH_HOME——state_root 的第二档,一并与真实环境隔离)
@@ -182,8 +192,57 @@ def main():
182
192
  "_probe_root" in mwin and "_probe_aux" in mwin
183
193
  and "except ValueError" in mwin and "return 2" in mwin,
184
194
  "main() 首段缺承接受理")
195
+ check("5g _abs_host_path 源窗口含 _reserved_device_tail( 调用"
196
+ "(末段直判与设备前缀判据并置,issue #57)",
197
+ "_reserved_device_tail(" in _func_window(src, "_abs_host_path"))
198
+
199
+ # ---- 7. 末段直判谓词表(issue #57):_reserved_device_tail 纯函数窗口 ----
200
+ # 谓词表取自实测闭集:会被吞的形态必须全判真(含变形 aux.txt/aux.tar.gz/
201
+ # "aux."/"aux "(尾空格)/aux..b 与绝对路径末段/带尾分隔符形态);不吞的
202
+ # 形态必须全判假——com0/lpt0/com10/lpt10 数字越界、auxx/auxiliary 非设备名、
203
+ # ".aux" 名字以点开头不拆扩展名。纯函数不触盘、无平台分支,Windows/POSIX 同判。
204
+ print("[7] 末段直判谓词表(_reserved_device_tail,纯字符串·平台无关)")
205
+ for case in ("aux", "AUX", "Aux", "aux.txt", "aux.tar.gz", "aux.",
206
+ "aux ", "aux..b", "con", "prn", "nul", "com1", "com9",
207
+ "lpt1", "lpt9", "com\u00b9", "com\u00b2", "com\u00b3",
208
+ "lpt\u00b9", "lpt\u00b2", "lpt\u00b3", "conin$", "conout$",
209
+ "D:\\probe\\aux", "D:\\probe\\aux\\", "D:\\probe\\aux.txt"):
210
+ check(f"7·真·{case!r}: 判为保留设备名末段(须拦)",
211
+ _reserved_device_tail(case), "判为 False(漏拦)")
212
+ for case in ("com0", "lpt0", "com10", "lpt10", "auxx", ".aux",
213
+ "auxiliary", "", "D:\\probe\\com0"):
214
+ check(f"7·假·{case!r}: 不判保留设备名末段(须放行)",
215
+ not _reserved_device_tail(case), "判为 True(过拦)")
216
+
217
+ # ---- 8. issue #57 现场模拟:abspath 不改写绝对路径(新 Windows 构建)----
218
+ # 报告人系统(较新 Windows 构建)的 GetFullPathNameW 不再把「<目录>\aux」
219
+ # 吞成 \\.\aux——既有前缀判据(r.startswith("\\\\.\\"))随之失效(本机旧
220
+ # 构建仍吞、复现不了该红,故以恒等替换模拟)。本段把 datapath 模块所用的
221
+ # os.path.abspath 临时换成恒等函数(保存/还原,finally),模拟「abspath
222
+ # 原样返回绝对路径」的报告人行为,再经 _probe_reject 探 aux_root。
223
+ # 变异自证锚点:删掉 _abs_host_path 条件里的 `or _reserved_device_tail(r)`
224
+ # 后,本段在 Windows 上必须转红(r 不以 \\.\ 开头 → 不抛 → 8a/8b 失败),
225
+ # 而 [7] 谓词表保持全绿(证明末段判定是承重件而非装饰)。
226
+ print("[8] issue #57 现场模拟:abspath 恒等(新构建不吞设备名)")
227
+ _abspath_orig = os.path.abspath
228
+ try:
229
+ os.path.abspath = lambda p: p
230
+ raised, msg = _probe_reject(aux_root, ENV_AUX_ROOT, "aux", tmp)
231
+ finally:
232
+ os.path.abspath = _abspath_orig
233
+ if os.name == "nt":
234
+ check("8a abspath 不改写时 aux_root() 仍抛 ValueError(末段直判承重)",
235
+ raised, f"msg={msg[:160]!r}")
236
+ check("8b 该路径下消息仍含覆盖键名 MDCG_AUX_ROOT",
237
+ ENV_AUX_ROOT in msg, f"msg={msg[:160]!r}")
238
+ else:
239
+ check("8a 非 Windows 不判定:abspath 恒等后仍不抛(平台闸未越界)",
240
+ not raised, f"msg={msg[:160]!r}")
241
+ check("8b 非 Windows 不判定:不产生拒因(消息为空)",
242
+ msg == "", f"msg={msg[:160]!r}")
185
243
 
186
244
  # ---- 6. 守卫自检 floor ----
245
+ # [6] 后置于 [7][8] 之后:FLOOR 随新增断言数上调,须统计全部断言。
187
246
  print("[6] 守卫自检")
188
247
  check(f"6a 断言总数 ≥ {FLOOR}(防探针面失效假绿)",
189
248
  PASS + FAIL >= FLOOR, f"total={PASS + FAIL}")
@@ -135,7 +135,7 @@ _STALE_SNIPPET = (
135
135
  "import io, json, os\n"
136
136
  "from md_cg import generation as g\n"
137
137
  "target = os.path.join(os.path.dirname(g.__file__), 'fsutil.py')\n"
138
- "raw = io.open(target, encoding='utf-8').read()\n"
138
+ "raw = io.open(target, encoding='utf-8', newline='').read()\n"
139
139
  "clean = g.is_stale()\n"
140
140
  "io.open(target, 'w', encoding='utf-8', newline='').write(raw + '\\n# 守卫临时改动\\n')\n"
141
141
  "modified = g.is_stale()\n"
@@ -177,8 +177,11 @@ def check_verify_and_mutation(tmp):
177
177
  _ok("一·全绿", "verify_delegations() 通过,且副本与当前树同代 %s" % real_fp)
178
178
 
179
179
  # 二:定点变异——改名 mdcg.py 里的 mint_auto_id 定义
180
+ # 读写都 newline=""(字节忠实):恢复必须逐字节复原——默认读会把 CRLF
181
+ # 压成 LF、再以 newline="" 写回即静默改变文件(2026-10-05 复核定因:
182
+ # 该非对称曾使「三·判别力」在 CRLF 工作区下必红)。
180
183
  mdcg_path = os.path.join(tmp, "md_cg", "mdcg.py")
181
- with open(mdcg_path, encoding="utf-8") as fh:
184
+ with open(mdcg_path, encoding="utf-8", newline="") as fh:
182
185
  original = fh.read()
183
186
  mutated = original.replace("def mint_auto_id(", "def mint_auto_id_renamed(", 1)
184
187
  if mutated == original:
@@ -0,0 +1,182 @@
1
+ # -*- coding: utf-8 -*-
2
+ """test_ghostref · 幽灵引用检查器(A2):短语层标记 + id 层降级告警(不拒收)。
3
+
4
+ 多主体世界模型对齐 v0.1 §2 L1「转引不得升级」的行为守卫。
5
+ 运行:python -m md_cg.test_ghostref
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import tempfile
11
+ import time
12
+
13
+ from . import branches, ghostref, nodefile
14
+ from .mdcos import MdCGOS
15
+ from .writepipe import default_pipeline
16
+
17
+ _ok = 0
18
+ _fail = []
19
+
20
+
21
+ def check(name, cond, detail=""):
22
+ global _ok
23
+ if cond:
24
+ _ok += 1
25
+ print("[ok] " + name)
26
+ else:
27
+ _fail.append(name)
28
+ print("[FAIL] %s · %s" % (name, str(detail)[:240]))
29
+
30
+
31
+ H = ("# 功能名:幽灵引用守卫\n# 生效条件:测试环境\n# 子功能:无\n"
32
+ "# 执行:无\n# 验证方式:test\n# 不适用条件:无\n")
33
+ URF = nodefile.UNCERTAIN_REFS_FIELD
34
+
35
+
36
+ def main():
37
+ tmp = tempfile.mkdtemp(prefix="mdcg_ghost_test_")
38
+ cg = MdCGOS(os.path.join(tmp, "root"))
39
+ pipe = default_pipeline()
40
+
41
+ def W(nid, body, **kw):
42
+ a = {"op": "write", "node_id": nid, "content": H + body + "\n",
43
+ "layer": "knowledge", "content_kind": "text",
44
+ "verification_basis": "test"}
45
+ a.update(kw)
46
+ return pipe.execute(cg, a)
47
+
48
+ def FM(nid):
49
+ return ((cg.get(nid) or {}).get("frontmatter")) or {}
50
+
51
+ # ---- 1. 短语层:无可解析出处 ⇒ 标记;**不拒收** ----
52
+ out1 = W("ghp_one", "可以允许你在帐篷里点那根你上次买的香薰蜡烛。")
53
+ check("T1a 幽灵回指仍落盘(不拒收)", out1.get("committed") is True,
54
+ {k: out1.get(k) for k in ("ok", "committed", "moved_to")})
55
+ _refs1 = FM("ghp_one").get(URF) or []
56
+ check("T1b 短语标记落 fm(上次买)", "上次买" in _refs1, _refs1)
57
+ check("T1c 出口带 ghost_refs 告警",
58
+ bool(out1.get("ghost_refs")) and "上次买" in out1["ghost_refs"],
59
+ out1.get("ghost_refs"))
60
+
61
+ # ---- 2. 有出处(句内含库内 id)⇒ 不标记 ----
62
+ W("base_node", "集市采购的凭证记录。")
63
+ out2 = W("ghp_two", "上次说的那件事,见 base_node 的记载。")
64
+ check("T2a 有出处的回指不落标记",
65
+ URF not in FM("ghp_two"), FM("ghp_two").get(URF))
66
+ check("T2b 有出处的回指建 reference 边",
67
+ any(e.get("target") == "base_node" for e in (FM("ghp_two").get("edges") or [])),
68
+ FM("ghp_two").get("edges"))
69
+ check("T2c 出口无 ghost_refs", not out2.get("ghost_refs"), out2.get("ghost_refs"))
70
+
71
+ # ---- 3. 无回指短语 ⇒ 不标记(对照)----
72
+ out3 = W("ghp_three", "集市购物,周末进行。")
73
+ check("T3a 无短语不落标记", URF not in FM("ghp_three"), FM("ghp_three").get(URF))
74
+ # 2026-10-05 复核实证:T3 在「写入被拒」时会假通过(无标记≠已落盘)——
75
+ # 补 committed 断言把「对照场景确实落盘」钉住。
76
+ check("T3b 对照场景确实落盘", out3.get("committed") is True,
77
+ out3.get("committed"))
78
+
79
+ # ---- 4. 幽灵 id(句内 id 形态但不在库)⇒ 无出处 ⇒ 标记 ----
80
+ W("ghp_four", "参照 ghost_absent 那条记载,明明讲过。")
81
+ _refs4 = FM("ghp_four").get(URF) or []
82
+ check("T4a 幽灵 id 句仍标记(明明讲过)", "明明讲过" in _refs4, _refs4)
83
+ check("T4b 幽灵 id 不建边", not (FM("ghp_four").get("edges") or []),
84
+ FM("ghp_four").get("edges"))
85
+
86
+ # ---- 5. id 层:目标晚于自身 ⇒ 降级告警(不拒收)----
87
+ W("late_target", "晚出现的凭证节点。")
88
+ out5 = W("late_src", "依据见 late_target 记载。",
89
+ valid_from=time.time() - 100000)
90
+ check("T5a 目标晚于自身仍落盘(不拒收)", out5.get("committed") is True,
91
+ out5.get("committed"))
92
+ _lr = out5.get("late_refs") or []
93
+ check("T5b 降级告警点名目标",
94
+ len(_lr) == 1 and _lr[0].get("target") == "late_target", _lr)
95
+
96
+ # ---- 6. 正常时序 ⇒ 无告警(对照)----
97
+ out6 = W("ok_src", "依据见 late_target 记载。",
98
+ valid_from=time.time() + 100)
99
+ check("T6a 正常时序无告警", not out6.get("late_refs"), out6.get("late_refs"))
100
+ check("T6b 对照场景确实落盘", out6.get("committed") is True,
101
+ out6.get("committed"))
102
+
103
+ # ---- 7. opt-out(ghostref=False)⇒ 不标记 ----
104
+ out7 = W("ghp_opt", "上次说的事。", ghostref=False)
105
+ check("T7a opt-out 仍落盘", out7.get("committed") is True, out7.get("committed"))
106
+ check("T7b opt-out 不落标记", URF not in FM("ghp_opt"),
107
+ FM("ghp_opt").get(URF))
108
+
109
+ # ---- 8. add 层:显式落键 / 覆写不继承 / 非法拒(直调,不经写链)----
110
+ cg.add("ghp_eight", H + "上次说的那件事。\n", layer="knowledge",
111
+ verification_basis="test", uncertain_refs=["上次说", "之前说"])
112
+ check("T8a 显式落键", FM("ghp_eight").get(URF) == ["上次说", "之前说"],
113
+ FM("ghp_eight").get(URF))
114
+ cg.add("ghp_eight", H + "改写后的干净正文。\n", layer="knowledge",
115
+ verification_basis="test")
116
+ check("T8b 覆写不继承(正文属性重算)", URF not in FM("ghp_eight"),
117
+ FM("ghp_eight").get(URF))
118
+ try:
119
+ cg.add("ghp_bad", H + "正文。\n", layer="knowledge",
120
+ verification_basis="test", uncertain_refs="上次的")
121
+ check("T8c 非法类型拒绝", False, "未抛 ValueError")
122
+ except ValueError:
123
+ check("T8c 非法类型拒绝", True)
124
+
125
+ # ---- 9. fork 副本携带标记 ----
126
+ r9 = branches.fork(cg, ["ghp_one"], branch_id="br_ghost")
127
+ _bid = (r9.get("forked") or [{}])[0].get("to")
128
+ check("T9a fork 成功", bool(r9.get("ok")) and bool(_bid), r9)
129
+ check("T9b 副本携带标记", (_bid and FM(_bid).get(URF) == FM("ghp_one").get(URF)),
130
+ (_bid, FM(_bid).get(URF) if _bid else None))
131
+
132
+ # ---- 11. rewrite 按新正文重算标记(复核 yellow-1 修)----
133
+ # 副本 ghp_one@br_ghost 有标记 ["上次买"](T1/ fork 携带):
134
+ # 改成干净正文 → 标记消失;改回幽灵正文 → 标记出现(双向,非「清空」)。
135
+ rr1 = branches.rewrite(cg, _bid, H + "改写为干净正文。\n")
136
+ check("T11a rewrite 成功", rr1.get("ok") is True, rr1)
137
+ check("T11b rewrite 干净正文 → 标记重算消失", URF not in FM(_bid),
138
+ FM(_bid).get(URF))
139
+ rr2 = branches.rewrite(cg, _bid, H + "之前说的那件事。\n")
140
+ check("T11c rewrite 幽灵正文 → 标记重算出现",
141
+ "之前说" in (FM(_bid).get(URF) or []), FM(_bid).get(URF))
142
+
143
+ # ---- 12. merge 回主支按分支正文重算(复核 yellow-2① 修)----
144
+ # 主支幽灵正文 → fork → rewrite 分支为干净 → merge 回主支 → 主支标记消失。
145
+ W("ghp_merge", "明明讲过的那件事。")
146
+ check("T12a 主支初始有标记", "明明讲过" in (FM("ghp_merge").get(URF) or []),
147
+ FM("ghp_merge").get(URF))
148
+ r12 = branches.fork(cg, ["ghp_merge"], branch_id="br_mg")
149
+ _bid12 = (r12.get("forked") or [{}])[0].get("to")
150
+ branches.rewrite(cg, _bid12, H + "干净正文(无回指)。\n")
151
+ rm = branches.merge(cg, "br_mg")
152
+ check("T12b merge 成功", rm.get("ok") is True, rm)
153
+ check("T12c 主支标记按分支正文重算(消失)", URF not in FM("ghp_merge"),
154
+ FM("ghp_merge").get(URF))
155
+ # 反方向(2026-10-05 自证补):干净主支 → 分支改幽灵 → merge 回写 ⇒ 主支
156
+ # 标记应**出现**——「消失」方向抓「携带旧值」,「出现」方向抓「置空不重算」。
157
+ W("ghp_merge2", "干净正文示例。")
158
+ r13 = branches.fork(cg, ["ghp_merge2"], branch_id="br_mg2")
159
+ _bid13 = (r13.get("forked") or [{}])[0].get("to")
160
+ branches.rewrite(cg, _bid13, H + "上次说的那件事。\n")
161
+ branches.merge(cg, "br_mg2")
162
+ check("T12d 主支标记按分支正文重算(出现)",
163
+ "上次说" in (FM("ghp_merge2").get(URF) or []),
164
+ FM("ghp_merge2").get(URF))
165
+
166
+ # ---- 10. 纯函数级:短语去重保序 + late_targets 边界 ----
167
+ # 「上次说的那件」只命中「上次说」——「上次的」要求连续子串(说/的 之间
168
+ # 不跳字);命中去重后跨句保序。
169
+ _pf = ghostref.find_ghost_phrases("上次说的事;上次说的那件;之前说的那批。")
170
+ check("T10a 短语去重保序", _pf == ["上次说", "之前说"], _pf)
171
+ check("T10b late_targets 无 own_from 直接空",
172
+ ghostref.late_targets(cg, ["late_target"], None) == [])
173
+ check("T10c late_targets 非数 own_from 空",
174
+ ghostref.late_targets(cg, ["late_target"], "昨天") == [])
175
+
176
+ print("\ntest_ghostref: %d 通过 / %d 失败" % (_ok, len(_fail)))
177
+ if _fail:
178
+ raise SystemExit(1)
179
+
180
+
181
+ if __name__ == "__main__":
182
+ main()
@@ -62,7 +62,7 @@ def main():
62
62
  check("3a 合规 verdict 通过门禁", isinstance(v, dict) and v["passed"] == 27)
63
63
  for name, bad in [
64
64
  ("3b 绝对路径拦截",
65
- {**v, "details": [{"log": r"D:\program\secret\path.txt"}]}),
65
+ {**v, "details": [{"log": r"D:\secret\path.txt"}]}),
66
66
  ("3c prompt 字段拦截",
67
67
  {**v, "prompt": "你是一个…"}),
68
68
  ("3d API key 拦截",
@@ -11,7 +11,7 @@
11
11
  (c) 截断完全静默(返回体无任何标记)——「读不全」与「读不到」不可区分;
12
12
  (d) 被排除集合随索引重排漂移(运行期 dict 追加 vs 重建后 sorted 写盘)。
13
13
  三接口 timeline/anchors/consistency 共用 `_scan` 同病;MCP 面 `_stg_call` 不透传
14
- max_scan(永远默认口径);DSH 侧 hooks.ts:423 auto-recall 直连 timeline 默认口径。
14
+ max_scan(永远默认口径);DSH 侧 hooks.ts:432 auto-recall 直连 timeline 默认口径。
15
15
 
16
16
  本批修法(设计者裁定 2026-10-03,原文):「遇到更多的检索节点,应该要建立索引,
17
17
  明确检索条件,和不适用条件,而不是扩大节点数」⇒ 只落「条件先行 + 截断可观测」