@furongjun1999/dsh-memory 0.7.1 → 0.7.3

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 (125) hide show
  1. package/README.md +58 -48
  2. package/codebuddy/CODEBUDDY.md +9 -9
  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/20309_/346/243/200/347/264/242/351/235/242/344/270/211/346/211/271/346/224/266/345/217/243_v1.0.md +1 -1
  27. package/docs/eval//345/217/221/345/270/20310_/350/257/273/351/235/242/346/215/237/345/235/217UTF8/345/256/266/346/227/217/346/224/266/345/217/243_v1.0.md +71 -0
  28. 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
  29. 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
  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/270/200/346/211/271_v1.0.md +195 -0
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  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_v26.md +196 -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_v27.md +259 -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_v28.md +217 -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_v29.md +270 -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_v30.md +231 -0
  41. 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
  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//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +42 -41
  49. package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +29 -8
  50. 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
  51. 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
  52. 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
  53. 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
  54. 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
  55. 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 +9 -8
  56. 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
  57. package/dsh/cordis-patch-profile-web.example.yml +2 -2
  58. package/lib/bridge.js +9 -0
  59. package/lib/hooks.d.ts +8 -1
  60. package/lib/hooks.js +94 -32
  61. package/lib/lib/hook_audit.d.ts +55 -0
  62. package/lib/lib/hook_audit.js +208 -0
  63. package/lib/lib/mutual.js +5 -1
  64. package/lib/lib/roleplay_web.js +17 -2
  65. package/md_cg/auditview.py +248 -0
  66. package/md_cg/bench6_arms.py +10 -6
  67. package/md_cg/bench_e2e_judge.py +4 -2
  68. package/md_cg/branches.py +29 -3
  69. package/md_cg/census.py +2 -1
  70. package/md_cg/consistency.py +38 -13
  71. package/md_cg/datapath.py +38 -11
  72. package/md_cg/forgetting.py +15 -2
  73. package/md_cg/freshness.py +32 -11
  74. package/md_cg/ghostref.py +130 -0
  75. package/md_cg/judgment_manifest.py +7 -0
  76. package/md_cg/mcp_server.py +58 -1
  77. package/md_cg/mdcg.py +4923 -4764
  78. package/md_cg/mdcos.py +40 -15
  79. package/md_cg/mreview/locate.py +1 -1
  80. package/md_cg/nodefile.py +77 -13
  81. package/md_cg/readcache.py +29 -4
  82. package/md_cg/rollback.py +12 -3
  83. package/md_cg/routing.py +10 -1
  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/sustain.py +1436 -1436
  89. package/md_cg/test_auditview.py +277 -0
  90. package/md_cg/test_autonomy_modes.py +2 -1
  91. package/md_cg/test_boot_openblas_guard.py +197 -0
  92. package/md_cg/test_ccg_form_parity.py +95 -4
  93. package/md_cg/test_cons200_scan_selection.py +4 -2
  94. package/md_cg/test_corrupt_utf8_read_surfaces.py +451 -0
  95. package/md_cg/test_datapath_device_name.py +62 -3
  96. package/md_cg/test_generation_guard.py +20 -11
  97. package/md_cg/test_ghostref.py +182 -0
  98. package/md_cg/test_health_corrupt_utf8.py +37 -0
  99. package/md_cg/test_index_crossprocess_reload.py +39 -24
  100. package/md_cg/test_interop.py +1 -1
  101. package/md_cg/test_issue52_scan_condition_first.py +1 -1
  102. package/md_cg/test_issue53_cipher_health_hash.py +385 -0
  103. package/md_cg/test_lock.py +16 -7
  104. package/md_cg/test_m3_h9_semantic_guard.py +1 -1
  105. package/md_cg/test_n230_dirty_replay.py +375 -375
  106. package/md_cg/test_n238_body_text_parity.py +156 -0
  107. package/md_cg/test_n276_probe_hoist.py +199 -0
  108. package/md_cg/test_p4_freshness.py +95 -1
  109. package/md_cg/test_policy_required_ccg.py +3 -1
  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_events.py +178 -0
  114. package/md_cg/test_time_core_lint.py +8 -1
  115. package/md_cg/test_writepipe.py +8 -5
  116. package/md_cg/tokens.py +11 -2
  117. package/md_cg/writepipe.py +76 -0
  118. package/package.json +2 -2
  119. package/skills/plugin.json +1 -1
  120. package/src/bridge.ts +9 -0
  121. package/src/hooks.ts +576 -517
  122. package/src/lib/hook_audit.ts +243 -0
  123. package/src/lib/mutual.ts +5 -1
  124. package/src/lib/roleplay_web.ts +17 -2
  125. package/zcode/AGENTS.md +9 -9
@@ -0,0 +1,385 @@
1
+ # -*- coding: utf-8 -*-
2
+ # 功能名:issue #53 三缺陷守卫——密文域标签(A) / 桶计数单点(B) / 写盘口内容指纹同步(C)
3
+ # 生效条件:md_cg/routing.py 的 `classify_text` 密文拦截与「单桶」文案、md_cg/mdcg.py 的
4
+ # `backfill_big_domain` 密文计数分支 / `_set_index_entry`+`_bucket_shift` 桶计数
5
+ # 单点 / `_write_node` 内容指纹同步 / `health` 的 bucket_scope 自述在位时成立;
6
+ # 沙箱条件:所有库根一律 tempfile.mkdtemp,绝不触在役库与在役服务;
7
+ # 本文件只读源码,变异时只写目标文件且在 finally 里按原始字节复原。
8
+ # 子功能:钉消费方看得见的目标语义(报告 issue #53 三条逐条对应):
9
+ # A 密文不得被当可分类正文:`classify_text(密文)` 恒 None(含 GDP 撞词形态);
10
+ # backfill 不写密文节点标签、ciphertext 计数单列且各计数为互斥分区;明文对照不误伤
11
+ # B 桶计数是单点事务:同桶覆写零动作、换桶撤旧加新、删除归零删键、读数自洽
12
+ # (nodes==带桶条目数)且带 bucket_scope 覆盖面自述
13
+ # C 重封(密文重写)必须同步索引内容指纹:update_tags/append_edge 后 entry.content_hash
14
+ # 仍落在盘面双形态内、reconcile 零假漂移、跨 close/重开仍零假漂移
15
+ # 执行:python -X utf8 -m md_cg.test_issue53_cipher_health_hash
16
+ # python -X utf8 -m md_cg.test_issue53_cipher_health_hash --mutation=all
17
+ # 验证方式:本文件自跑(自带断言计数、live 计数与退出码 rc=0/1);定点变异自证见
18
+ # --mutation(把每处修复逐点抽回改动前语义 → 本守卫必红且红项名可贴出;
19
+ # 变异在 finally 中按原始字节复原并回读核对逐字节相等 → 无残余)。
20
+ # 不适用条件:情感交互|闲聊|纯查询无改动;routing 阈值面与既有区块守卫(test_m3_h9 /
21
+ # test_retr_s1)的判据面不在本席改动面内。
22
+ from __future__ import annotations
23
+
24
+ import os
25
+ import shutil
26
+ import subprocess
27
+ import sys
28
+ import tempfile
29
+
30
+ # ── 隔离前置:在任何 md_cg 子模块 import 之前把根指到临时目录 ──
31
+ _TMP = tempfile.mkdtemp(prefix="i53_")
32
+ os.environ["MDCG_AUX_ROOT"] = os.path.join(_TMP, "auxroot")
33
+ os.environ["MDCG_ROOT"] = os.path.join(_TMP, "cgroot")
34
+ for _k in ("MDCG_TOKEN", "MDCG_TENANT", "MDCG_CLEARANCE", "MDCG_SESSION",
35
+ "DSH_SESSION_ID", "MDCG_LEGACY_ENV_AUTH", "MDCG_LEGACY_ENV_ADMIN",
36
+ "MDCG_CAN_ADMIN", "MDCG_CAN_WRITE", "MDCG_STATE_ROOT",
37
+ "MDCG_DATA_ROOT", "MDCG_TENANT_REGISTRY", "MDCG_VERIFIER_MODULES"):
38
+ os.environ.pop(_k, None)
39
+ os.environ["MDCG_RETRIEVAL_PIPELINE"] = "1" # A/B 组前置(回填/域标签面)
40
+
41
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
42
+
43
+ from md_cg import nodefile, routing # noqa: E402
44
+ from md_cg.mdcg import MdCG # noqa: E402
45
+ from md_cg.mdcos import MdCGOS, MdCGSecure # noqa: E402
46
+ from md_cg.security import Principal # noqa: E402
47
+
48
+ try:
49
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
50
+ except Exception: # noqa: BLE001
51
+ pass
52
+
53
+ _REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
54
+
55
+ PASS = FAIL = LIVE = 0
56
+ LIVE_FLOOR = 10
57
+ FAILS = []
58
+ _OPEN = []
59
+
60
+ TEST_KEY = bytes(range(32)) # 合成测试钥;绝不触碰真实主密钥面
61
+
62
+ #: GDP 三连是唯一实测命中形态(BIG_DOMAINS 里唯一的拉丁词是「经济」的 GDP)
63
+ CIPHER_GDP = "<!-- mdcg-enc:v1:QUJDREVGGDPQUJDREVG -->"
64
+ CIPHER_PLAIN = "<!-- mdcg-enc:v1:QUJDREVGQUJDREVG -->"
65
+ BODY = ("# 功能名:issue53 探针\n"
66
+ "# 生效条件:守卫临时库\n"
67
+ "# 子功能:素材节点\n"
68
+ "# 执行:cg.add\n"
69
+ "# 验证方式:本守卫断言\n"
70
+ "# 不适用条件:无\n"
71
+ "守卫探针正文。\n")
72
+
73
+
74
+ def check(name, cond, detail="", live=False):
75
+ global PASS, FAIL, LIVE
76
+ if live:
77
+ LIVE += 1
78
+ if cond:
79
+ PASS += 1
80
+ print(" [PASS] %s" % name)
81
+ else:
82
+ FAIL += 1
83
+ FAILS.append(name)
84
+ print(" [FAIL] %s · %s" % (name, detail))
85
+
86
+
87
+ def _mk(rel, cls=MdCGOS, **kw):
88
+ cg = cls(os.path.join(_TMP, rel), autoflush=0, **kw)
89
+ _OPEN.append(cg)
90
+ return cg
91
+
92
+
93
+ def _put_raw(lib, nid, layer, content, sens=None):
94
+ """手工落一个节点文件(可造密文形态正文——基类写入不做封装)。"""
95
+ fm = {"id": nid, "layer": layer, "modality": "text", "importance": 0.5,
96
+ "confidence": 0.6, "condition_space": {"time_window": [1.0, 9e9]},
97
+ "tags": [], "created_at": 1.0, "access_count": 0, "last_access": 0,
98
+ "edges": [], "verification_basis": None,
99
+ "non_applicable_conditions": [], "evidence_count": 0,
100
+ "positive_evidence": 0, "negative_evidence": 0}
101
+ if sens:
102
+ fm["sensitivity"] = sens
103
+ p = os.path.join(lib, layer, nid + ".md")
104
+ os.makedirs(os.path.dirname(p), exist_ok=True)
105
+ with open(p, "w", encoding="utf-8") as f:
106
+ f.write(nodefile.dumps(fm, content))
107
+
108
+
109
+ def _disk_match(cg, nid):
110
+ """entry.content_hash 是否落在盘面双形态接受集(reconcile 同口径)。"""
111
+ e = cg.index["nodes"][nid]
112
+ p = os.path.join(cg.root, e["path"])
113
+ with open(p, encoding="utf-8") as f:
114
+ _fm, content = nodefile.loads(f.read())
115
+ raw = nodefile.content_hash(content or "")
116
+ norm = nodefile.content_hash((content or "").rstrip("\n"))
117
+ return e.get("content_hash") in (raw, norm), e.get("content_hash"), raw, norm
118
+
119
+
120
+ def _close_all():
121
+ for cg in _OPEN:
122
+ try:
123
+ cg.close()
124
+ except Exception: # noqa: BLE001
125
+ pass
126
+
127
+
128
+ def _cleanup():
129
+ import gc
130
+ import time
131
+ for _ in range(6):
132
+ shutil.rmtree(_TMP, ignore_errors=True)
133
+ if not os.path.exists(_TMP):
134
+ return
135
+ gc.collect()
136
+ time.sleep(0.25)
137
+
138
+
139
+ # ───────────────────────── A:密文域标签 ─────────────────────────
140
+
141
+ def group_a():
142
+ print("\n【A】issue #53-A:密文不得被当可分类正文")
143
+ check("A1 classify_text 对密文(含 GDP 撞词形态)恒 None",
144
+ routing.classify_text(CIPHER_GDP) is None
145
+ and routing.classify_text(CIPHER_PLAIN) is None,
146
+ "%r / %r" % (routing.classify_text(CIPHER_GDP),
147
+ routing.classify_text(CIPHER_PLAIN)))
148
+ check("A2 明文对照不误伤:GDP 明文字面仍归「经济」",
149
+ routing.classify_text("GDP 与市场") == "经济",
150
+ repr(routing.classify_text("GDP 与市场")))
151
+
152
+ lib = os.path.join(_TMP, "lib_a")
153
+ os.makedirs(lib, exist_ok=True)
154
+ _put_raw(lib, "a_cip_gdp", "contextual", CIPHER_GDP, sens="private")
155
+ _put_raw(lib, "a_cip_ok", "contextual", CIPHER_PLAIN, sens="private")
156
+ _put_raw(lib, "a_plain", "contextual", "# 功能名:x\n计算机与算法内容\n")
157
+ cg = MdCG(lib) # 基类=backfill CLI 的同款打开方式
158
+ st = cg.backfill_big_domain()
159
+ check("A3 回填不给密文写假标签(修前:GDP 形态被判「经济」并落盘)",
160
+ st["written"] == 1
161
+ and (cg.get("a_cip_gdp")["frontmatter"] or {}).get("big_domain") is None
162
+ and (cg.get("a_cip_ok")["frontmatter"] or {}).get("big_domain") is None
163
+ and cg.get("a_plain")["frontmatter"].get("big_domain") == "计算机",
164
+ str(st), live=True)
165
+ check("A4 ciphertext 独立计数且各计数为互斥分区(和 == seen)",
166
+ st.get("ciphertext") == 2
167
+ and (st["written"] + st["already"] + st["no_signal"]
168
+ + st["unreadable"] + st["ciphertext"] + st["index_synced"]
169
+ == st["seen"]),
170
+ str(st), live=True)
171
+ st2 = cg.backfill_big_domain()
172
+ check("A5 二次回填:密文仍走 ciphertext(不被 already 假确认、不重写)",
173
+ st2.get("ciphertext") == 2 and st2["already"] == 1
174
+ and st2["written"] == 0, str(st2))
175
+ cg.close()
176
+
177
+
178
+ # ───────────────────────── B:桶计数单点 ─────────────────────────
179
+
180
+ def group_b():
181
+ print("\n【B】issue #53-B:桶计数单点(覆写零虚高 / 换桶撤旧加新 / 归零删键)")
182
+ cg = _mk("lib_b")
183
+ cg.add("b1", BODY, layer="knowledge")
184
+ cg.flush()
185
+ check("B1a 首次写入计数 1(前置)",
186
+ dict(cg.index["buckets"]) == {"orphan": 1}, str(cg.index["buckets"]))
187
+ cg.add("b1", BODY + "覆写。\n", layer="knowledge")
188
+ check("B1b 同桶覆写不虚高(修前:{'orphan': 2})",
189
+ dict(cg.index["buckets"]) == {"orphan": 1}, str(cg.index["buckets"]),
190
+ live=True)
191
+ cg.flush()
192
+ cg.update_tags("b1", add=["t"])
193
+ check("B1c update_tags 不动桶计数(物理未动)",
194
+ dict(cg.index["buckets"]) == {"orphan": 1}, str(cg.index["buckets"]))
195
+
196
+ cg.add("b2", BODY, layer="knowledge", tags=["domain:甲"])
197
+ cg.flush()
198
+ b_old = routing.bucket_dir(routing.normalize_domain("甲"))
199
+ b_new = routing.bucket_dir(routing.normalize_domain("乙"))
200
+ has_old = b_old in cg.index["buckets"]
201
+ cg.add("b2", BODY + "换桶覆写。\n", layer="knowledge", tags=["domain:乙"])
202
+ check("B2 换桶覆写:旧桶撤、新桶加(净变化守恒)",
203
+ has_old and b_old not in cg.index["buckets"]
204
+ and cg.index["buckets"].get(b_new) == 1
205
+ and sum(cg.index["buckets"].values()) == 2,
206
+ "%s | old_in=%s" % (cg.index["buckets"], has_old), live=True)
207
+ cg.flush()
208
+
209
+ cg.forget("b2", reason="守卫归零删键探针")
210
+ check("B3 删除路径归零删键(counts 无 0 值残留、总数守恒)",
211
+ b_new not in cg.index["buckets"]
212
+ and sum(cg.index["buckets"].values()) == 1,
213
+ str(cg.index["buckets"]), live=True)
214
+
215
+ h = cg.health()
216
+ n_bucketed = sum(1 for e in list(cg.index["nodes"].values())
217
+ if e.get("bucket"))
218
+ check("B4 health 读数自洽且带覆盖面自述(nodes==带桶条目数 / bucket_scope)",
219
+ h.get("nodes") == n_bucketed
220
+ and h.get("bucket_scope") == ["knowledge"],
221
+ "nodes=%s n=%s scope=%s" % (h.get("nodes"), n_bucketed,
222
+ h.get("bucket_scope")), live=True)
223
+ check("B5 修复后单桶文案不再称「全库」(统计面已如实)",
224
+ any("单桶" in p for p in (h.get("problems") or []))
225
+ and not any("全库" in p for p in (h.get("problems") or [])),
226
+ str(h.get("problems")))
227
+ cg.close()
228
+
229
+
230
+ # ───────────────────────── C:写盘口内容指纹 ─────────────────────────
231
+
232
+ def group_c():
233
+ print("\n【C】issue #53-C:重封必须同步索引内容指纹(零假漂移)")
234
+ pri = Principal(clearance="secret", actor="i53")
235
+ lib = os.path.join(_TMP, "lib_c")
236
+ cg = _mk("lib_c", cls=MdCGSecure, principal=pri, master_key=TEST_KEY)
237
+ nid = cg.add("c_priv", BODY + "私密。\n", layer="contextual",
238
+ sensitivity="private")
239
+ cg.add("c_plain", BODY + "明文。\n", layer="contextual")
240
+ cg.flush()
241
+ m0 = _disk_match(cg, nid)
242
+ check("C1 写入后指纹即落入盘面双形态(前置)", m0[0], str(m0), live=True)
243
+ cg.update_tags(nid, add=["t1"])
244
+ m1 = _disk_match(cg, nid)
245
+ check("C2 update_tags 重封后指纹仍同步(修前:停在旧密文)",
246
+ m1[0] and m1[1] != m0[1], str(m1), live=True)
247
+ cg.append_edge(nid, {"target": "x", "relation_type": "derived_from"})
248
+ m2 = _disk_match(cg, nid)
249
+ check("C3 append_edge 重封后指纹仍同步", m2[0], str(m2))
250
+ cg.flush()
251
+ rep = cg.reconcile_state(apply=False)
252
+ check("C4 对账零假漂移(修前:hash_drift=['c_priv'])",
253
+ rep["hash_drift"] == [], str(rep["hash_drift"]), live=True)
254
+ cg.close()
255
+
256
+ # 跨 close / 重开:毒若未根治会随 compact 快照持久(issue #53-C 实测路径)
257
+ cg2 = _mk("lib_c", cls=MdCGSecure, principal=pri, master_key=TEST_KEY)
258
+ rep2 = cg2.reconcile_state(apply=False)
259
+ m3 = _disk_match(cg2, nid)
260
+ check("C5 重开后仍零假漂移且指纹同步(跨进程持久面)",
261
+ rep2["hash_drift"] == [] and m3[0], str(rep2["hash_drift"]),
262
+ live=True)
263
+ check("C6 明文节点对照:重写后指纹不漂(内容未变,零误伤)",
264
+ _disk_match(cg2, "c_plain")[0], str(_disk_match(cg2, "c_plain")))
265
+ cg2.close()
266
+
267
+
268
+ # ───────────────────────── 主流程 ─────────────────────────
269
+
270
+ def main():
271
+ print("【⓪】隔离自证")
272
+ check("0a 临时根内(不触在役库)",
273
+ os.path.abspath(_TMP).startswith(os.path.abspath(tempfile.gettempdir())),
274
+ _TMP, live=True)
275
+ group_a()
276
+ group_b()
277
+ group_c()
278
+ print("\n" + "=" * 66)
279
+ print("PASS=%d FAIL=%d LIVE=%d(floor=%d)" % (PASS, FAIL, LIVE, LIVE_FLOOR))
280
+ check("live 断言数不低于 floor", LIVE >= LIVE_FLOOR, "live=%d" % LIVE)
281
+ if FAILS:
282
+ print("失败项:" + ";".join(FAILS))
283
+ return 0 if FAIL == 0 else 1
284
+
285
+
286
+ # =========================================================================
287
+ # 定点变异(把每处修复逐点抽回改动前语义)——字节保真改写 → 跑 → finally 复原
288
+ # =========================================================================
289
+
290
+ def _read_bytes(rel):
291
+ with open(os.path.join(_REPO, rel), "rb") as f:
292
+ return f.read()
293
+
294
+
295
+ def _j(*lines):
296
+ return "\n".join(lines) + "\n"
297
+
298
+
299
+ MUTATIONS = {
300
+ # A:classify_text 单点去掉密文拦截 ⇒ A1 红
301
+ "a_classify": ("md_cg/routing.py",
302
+ " if crypto.is_encrypted(text):\n return None\n",
303
+ " if False:\n return None\n"),
304
+ # A:backfill 显式计数分支抽掉(单点仍在,故 A3 仍绿、A4 计数红)
305
+ "a_backfill": ("md_cg/mdcg.py",
306
+ " if crypto.is_encrypted(content):\n",
307
+ " if False:\n"),
308
+ # B:桶计数退回「同 id 无条件 +1」⇒ B1b 红
309
+ "b_bucket": ("md_cg/mdcg.py",
310
+ _j(" if _ob != _nb:",
311
+ " self._bucket_shift(_ob, -1)",
312
+ " self._bucket_shift(_nb, +1)"),
313
+ _j(" if _nb:",
314
+ " self._bucket_shift(_nb, +1)")),
315
+ # C:写盘口指纹同步段抽掉 ⇒ C2/C4 红
316
+ "c_hash": ("md_cg/mdcg.py",
317
+ _j(" _entry = self.index[\"nodes\"].get(node_id)",
318
+ " if _entry is not None:",
319
+ " _entry[\"content_hash\"] = nodefile.content_hash(sealed)"),
320
+ _j(" _entry = None",
321
+ " if _entry is not None:",
322
+ " _entry[\"content_hash\"] = nodefile.content_hash(sealed)")),
323
+ }
324
+
325
+
326
+ def _run_child():
327
+ env = dict(os.environ)
328
+ env["PYTHONUTF8"] = "1"
329
+ p = subprocess.run([sys.executable, "-X", "utf8", "-m",
330
+ "md_cg.test_issue53_cipher_health_hash"],
331
+ cwd=_REPO, env=env, capture_output=True, text=True,
332
+ encoding="utf-8", errors="replace")
333
+ return p.returncode, (p.stdout or "") + (p.stderr or "")
334
+
335
+
336
+ def _mutate(name):
337
+ rel, old, new = MUTATIONS[name]
338
+ path = os.path.join(_REPO, rel)
339
+ raw = _read_bytes(rel)
340
+ text = raw.decode("utf-8")
341
+ if text.count(old) != 1:
342
+ raise SystemExit("[MUT] %s:锚点命中 %d 次(应为 1)——树已变,请复核"
343
+ % (name, text.count(old)))
344
+ try:
345
+ with open(path, "wb") as f:
346
+ f.write(text.replace(old, new, 1).encode("utf-8"))
347
+ rc, out = _run_child()
348
+ finally:
349
+ with open(path, "wb") as f:
350
+ f.write(raw)
351
+ restored = _read_bytes(rel) == raw
352
+ reds = [ln.strip() for ln in out.splitlines() if "[FAIL]" in ln]
353
+ print("\n" + "=" * 66)
354
+ print("[MUT] %s(%s)→ 子进程 rc=%d" % (name, rel, rc))
355
+ for ln in reds:
356
+ print(" RED " + ln)
357
+ print("[MUT] 红项数 = %d" % len(reds))
358
+ print("[MUT] 恢复核对:%s 逐字节复原=%s" % (rel, restored))
359
+ return rc, len(reds), restored
360
+
361
+
362
+ if __name__ == "__main__":
363
+ args = list(sys.argv[1:])
364
+ _mut = [a for a in args if a.startswith("--mutation")]
365
+ if _mut:
366
+ want = _mut[-1].split("=", 1)[1] if "=" in _mut[-1] else "all"
367
+ names = list(MUTATIONS) if want == "all" else [want]
368
+ bad = []
369
+ for nm in names:
370
+ if nm not in MUTATIONS:
371
+ raise SystemExit("未知变异:%s" % nm)
372
+ rc, nred, ok = _mutate(nm)
373
+ if rc == 0 or nred == 0 or not ok:
374
+ bad.append(nm)
375
+ print("\n[SUMMARY] 变异 %d 项;未变红或未复原的:%s"
376
+ % (len(names), bad or "无"))
377
+ _close_all()
378
+ _cleanup()
379
+ sys.exit(1 if bad else 0)
380
+ try:
381
+ rc = main()
382
+ finally:
383
+ _close_all()
384
+ _cleanup()
385
+ sys.exit(rc)
@@ -6,33 +6,42 @@ N 个进程各做 M 次「锁内 读-加一-写」。若锁真互斥,最终值
6
6
  import os
7
7
  import sys
8
8
  import subprocess
9
+ import tempfile
9
10
 
10
11
  sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
11
12
  from md_cg.fsutil import FileLock, atomic_write
12
13
 
13
- TARGET = os.path.join(os.path.dirname(os.path.abspath(__file__)), "_locktest.txt")
14
+ # 落点改**系统临时目录 + PID 唯一化**(2026-10-05 复核建议 6):原落包目录
15
+ # `md_cg/_locktest.txt`——并发跑测试/共享仓库检出(copytree、CI 并行)时
16
+ # 瞬态文件被无余量扫描撞上(复核两次实测 WinError 2),且不被 .gitignore
17
+ # 覆盖易误提交。路径在**父进程**唯一化后经 argv 传给 worker 子进程——
18
+ # 子进程是独立 PID,必须显式继承同一路径(v1 初改曾按 PID 各自计算 ⇒
19
+ # 父子读不同文件、计数归零的实测教训)。判据不变:N 进程对同一文件加锁。
20
+ TARGET = os.path.join(tempfile.gettempdir(),
21
+ "mdcg_locktest_%d.txt" % os.getpid())
14
22
 
15
23
 
16
- def worker(m):
24
+ def worker(m, target):
17
25
  for _ in range(m):
18
- with FileLock(TARGET) as lk:
26
+ with FileLock(target) as lk:
19
27
  if not lk.acquired:
20
28
  print("LOCK_TIMEOUT", file=sys.stderr)
21
29
  try:
22
- with open(TARGET, encoding="utf-8") as f:
30
+ with open(target, encoding="utf-8") as f:
23
31
  v = int(f.read().strip() or 0)
24
32
  except (OSError, ValueError):
25
33
  v = 0
26
- atomic_write(TARGET, str(v + 1))
34
+ atomic_write(target, str(v + 1))
27
35
 
28
36
 
29
37
  if __name__ == "__main__":
30
38
  if len(sys.argv) > 1 and sys.argv[1] == "--worker":
31
- worker(int(sys.argv[2]))
39
+ worker(int(sys.argv[2]), sys.argv[3])
32
40
  sys.exit(0)
33
41
  N, M = 6, 50
34
42
  atomic_write(TARGET, "0")
35
- ps = [subprocess.Popen([sys.executable, os.path.abspath(__file__), "--worker", str(M)])
43
+ ps = [subprocess.Popen([sys.executable, os.path.abspath(__file__), "--worker",
44
+ str(M), TARGET])
36
45
  for _ in range(N)]
37
46
  [p.wait() for p in ps]
38
47
  with open(TARGET, encoding="utf-8") as f:
@@ -4,7 +4,7 @@
4
4
  # 生效条件:md_cg/routing.py 的 `bucket_health(counts, total_nodes=None)`、md_cg/mdcg.py
5
5
  # 的 `MdCG.health` 呼叫点(把节点总数传进判据)、md_cg/protect.py 的 `mark`
6
6
  # 三处修复在位时成立;沙箱条件:所有库根一律 tempfile.mkdtemp,绝不触在役库
7
- # (D:/program/dsh-memory-main 的 in-service 库)与在役服务;本文件只读源码,
7
+ # (<仓根> 的 in-service 库)与在役服务;本文件只读源码,
8
8
  # 变异时只写目标文件且在 finally 里按原始字节复原。
9
9
  # 子功能:钉的是**消费方看得见的目标语义**,不是站点文本——
10
10
  # A M3 假警报方向:「从未分区」的库(单桶)不得被读成「分区退化」——读数里不得