@furongjun1999/dsh-memory 0.5.1 → 0.6.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 (187) hide show
  1. package/README.md +588 -552
  2. package/codebuddy/CODEBUDDY.md +10 -10
  3. package/data/policy.json +27 -0
  4. package/docs/discipline/harnesses.yaml +31 -0
  5. package/docs/discipline/templates/full.md.tmpl +1 -1
  6. package/docs/discipline/templates/rules.mdc.tmpl +1 -1
  7. package/docs/eval/DSH/346/227/245/345/277/227/347/264/242/345/274/225v2_/345/217/202/350/200/203dsh-TUI_v1.0.md +248 -0
  8. package/docs/eval/DSH/346/227/245/345/277/227/347/264/242/345/274/225/346/225/210/346/236/234/351/252/214/350/257/201_v1.0.md +209 -0
  9. package/docs/eval/DSH/347/253/257/347/274/272/351/231/267/344/270/223/351/241/271_v1.0.md +254 -0
  10. 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 +418 -0
  11. package/docs/eval/P1b2_/350/257/273/351/235/242/344/273/243/351/231/205/344/277/256/345/244/215_v1.0.md +124 -0
  12. 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 +441 -0
  13. 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 +398 -0
  14. 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 +498 -0
  15. 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 +669 -0
  16. 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 +683 -0
  17. package/docs/eval//345/217/221/345/270/20306_/344/270/200/351/224/256/351/205/215/347/275/256/344/270/216DSH0172_v1.0.md +171 -0
  18. 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 +205 -0
  19. package/docs/eval//346/200/247/350/203/275/344/270/223/351/241/271_/345/206/267/346/237/245/350/257/242/344/270/216/345/206/205/345/255/230_v1.0.md +218 -0
  20. package/docs/eval//346/225/205/351/232/234/346/263/250/345/205/245/345/256/236/346/265/213_v1.0.md +10 -0
  21. 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 +646 -0
  22. 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_v18.md +183 -0
  23. 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_v19.md +207 -0
  24. 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_v20.md +283 -0
  25. 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_v21.md +224 -0
  26. 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_v22.md +223 -0
  27. 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_v23.md +293 -0
  28. 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_v24.md +303 -0
  29. 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_v25.md +230 -0
  30. 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 +20 -0
  31. 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
  32. package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +12 -0
  33. 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
  34. package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +21 -9
  35. package/docs/mdcg//347/201/265/346/236/242/350/256/260/345/277/206/345/212/250/350/257/215/345/215/217/350/256/256_v1.0-draft.md +7 -2
  36. package/docs/mdcg//350/256/244/347/237/245/345/233/276_/347/264/242/345/274/225/344/270/216/345/267/245/347/250/213/350/247/204/350/214/203/345/214/226_/350/256/241/345/210/222_v0.1.md +39 -3
  37. 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 +220 -0
  38. package/docs/plans//347/234/237/346/272/220/347/264/242/345/274/225_/351/200/232/347/224/250/346/234/272/345/210/266_v0.3.md +227 -0
  39. package/docs/plans//350/234/202/345/267/242/346/250/241/345/236/213/345/257/206/351/222/245/351/205/215/347/275/256/351/235/242_v1.0.md +385 -0
  40. 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 +460 -460
  41. package/dsh/cordis-patch-profile-web.example.yml +35 -0
  42. package/lib/bridge.js +24 -2
  43. package/lib/cli.d.ts +3 -0
  44. package/lib/cli.js +66 -0
  45. package/lib/hooks.d.ts +31 -2
  46. package/lib/hooks.js +219 -16
  47. package/lib/init.d.ts +88 -0
  48. package/lib/init.js +302 -0
  49. package/lib/lib/prompt_safety.d.ts +52 -1
  50. package/lib/lib/prompt_safety.js +76 -1
  51. package/md_cg/audit.py +652 -379
  52. package/md_cg/bench6_arms.py +1 -1
  53. package/md_cg/bench_p0.py +1 -1
  54. package/md_cg/branches.py +2 -2
  55. package/md_cg/ccgc.py +1071 -1006
  56. package/md_cg/chain.py +1 -1
  57. package/md_cg/consistency.py +97 -9
  58. package/md_cg/consolidate.py +34 -9
  59. package/md_cg/crypto.py +59 -26
  60. package/md_cg/docindex.py +16 -1
  61. package/md_cg/evidence.py +585 -582
  62. package/md_cg/evolution.py +17 -1
  63. package/md_cg/export.py +1 -1
  64. package/md_cg/forgetting.py +309 -13
  65. package/md_cg/fsutil.py +454 -8
  66. package/md_cg/identity.py +3 -3
  67. package/md_cg/insight.py +24 -3
  68. package/md_cg/linkref.py +1 -1
  69. package/md_cg/logref.py +327 -0
  70. package/md_cg/mcp_server.py +4152 -3818
  71. package/md_cg/mdcg.py +4208 -3462
  72. package/md_cg/mdcos.py +4491 -4137
  73. package/md_cg/mreview/pipeline.py +15 -1
  74. package/md_cg/nodefile.py +639 -575
  75. package/md_cg/protect.py +158 -11
  76. package/md_cg/protocol.py +41 -2
  77. package/md_cg/provenance.py +22 -4
  78. package/md_cg/reach.py +4 -4
  79. package/md_cg/readcache.py +76 -15
  80. package/md_cg/reconcile.py +1 -1
  81. package/md_cg/refindex.py +246 -37
  82. package/md_cg/refine.py +1 -1
  83. package/md_cg/review_cli.py +49 -4
  84. package/md_cg/routing.py +32 -5
  85. package/md_cg/run_tests.py +17 -0
  86. package/md_cg/scrub.py +30 -3
  87. package/md_cg/security.py +47 -1
  88. package/md_cg/self_state.py +5 -5
  89. package/md_cg/sources.py +23 -6
  90. package/md_cg/srcindex.py +352 -0
  91. package/md_cg/stg.py +47 -3
  92. package/md_cg/subgraph.py +2 -2
  93. package/md_cg/sustain.py +1299 -1168
  94. package/md_cg/tasks.py +469 -470
  95. package/md_cg/test_b1_auto_id_multiproc.py +277 -0
  96. package/md_cg/test_b1b2_write_face.py +417 -0
  97. package/md_cg/test_b2_sensitivity_landing.py +223 -0
  98. package/md_cg/test_b3_merge_keeps_content.py +576 -0
  99. package/md_cg/test_b4_shard_dir_selfheal.py +843 -0
  100. package/md_cg/test_b4_shard_dir_selfheal_guard.py +238 -0
  101. package/md_cg/test_c3_transient_read_negative.py +793 -0
  102. package/md_cg/test_c8_search_rrf_gates.py +502 -0
  103. package/md_cg/test_ccg_form_parity.py +188 -0
  104. package/md_cg/test_ccgc.py +28 -3
  105. package/md_cg/test_govern_directread.py +17 -4
  106. package/md_cg/test_h2_session_view_norm.py +233 -0
  107. package/md_cg/test_h4_sustain_snapshot.py +1377 -0
  108. package/md_cg/test_hive_ingest.py +285 -285
  109. package/md_cg/test_index_crossprocess_reload.py +301 -0
  110. package/md_cg/test_issue39_utf8_stdio.py +307 -16
  111. package/md_cg/test_issue43_default_policy.py +865 -0
  112. package/md_cg/test_legacy_p3_node_id_type.py +375 -0
  113. package/md_cg/test_linkref.py +10 -3
  114. package/md_cg/test_lock.py +2 -2
  115. package/md_cg/test_logref.py +1109 -0
  116. package/md_cg/test_m3_h9_bucket_health_protect_mark.py +301 -0
  117. package/md_cg/test_m3_h9_semantic_guard.py +525 -0
  118. package/md_cg/test_mr_m2.py +15 -3
  119. package/md_cg/test_n130_verify_falsified_protect.py +1 -1
  120. package/md_cg/test_n139_dek_provision_failclosed.py +185 -0
  121. package/md_cg/test_n176_link_trust.py +221 -0
  122. package/md_cg/test_n178_units_jobid_gate.py +212 -0
  123. package/md_cg/test_n184_keys_concurrent_provision.py +307 -0
  124. package/md_cg/test_n195_writepath_reload.py +283 -0
  125. package/md_cg/test_n196_stale_gate_skip.py +169 -0
  126. package/md_cg/test_n197_n208_write_face_gates.py +471 -0
  127. package/md_cg/test_n198_tokens_corrupt_failclosed.py +225 -0
  128. package/md_cg/test_n199_tokens_concurrent_write.py +378 -0
  129. package/md_cg/test_n201_proposal_visibility.py +382 -0
  130. package/md_cg/test_n202_session_notes_visibility.py +461 -0
  131. package/md_cg/test_n204_n205_n226_n227_n228_n229_exit_gates.py +542 -0
  132. package/md_cg/test_n206_stdio_jsonrpc_type.py +403 -0
  133. package/md_cg/test_n209_verify_write_face_gates.py +461 -0
  134. package/md_cg/test_n212_n213_n224_generation_gates.py +513 -0
  135. package/md_cg/test_n214_n215_n221_n222_write_face_gates.py +563 -0
  136. package/md_cg/test_n225_nonobject_load.py +1110 -0
  137. package/md_cg/test_n62_tenant_bind_failclosed.py +200 -0
  138. package/md_cg/test_neg_condition_hits.py +333 -0
  139. package/md_cg/test_neg_tail_honesty.py +663 -0
  140. package/md_cg/test_none_id_write_guard.py +166 -0
  141. package/md_cg/test_opt_batch1_md_cg.py +451 -0
  142. package/md_cg/test_p1.py +5 -5
  143. package/md_cg/test_p11_consistency.py +161 -12
  144. package/md_cg/test_p26_refindex.py +2 -2
  145. package/md_cg/test_p27_docindex.py +777 -774
  146. package/md_cg/test_p28_refcheck.py +740 -13
  147. package/md_cg/test_p29_session_ingest_export.py +2 -2
  148. package/md_cg/test_p2_mcp.py +12 -1
  149. package/md_cg/test_p30_maintain.py +33 -2
  150. package/md_cg/test_p31_insight.py +1 -0
  151. package/md_cg/test_p9c_dedup_hints.py +276 -0
  152. package/md_cg/test_policy_required_ccg.py +426 -0
  153. package/md_cg/test_protocol.py +22 -0
  154. package/md_cg/test_rank_parity_score_mode.py +516 -0
  155. package/md_cg/test_read_face_input_gates.py +363 -0
  156. package/md_cg/test_read_face_semantics.py +489 -0
  157. package/md_cg/test_recall_face_guards.py +812 -0
  158. package/md_cg/test_rejected_credential_forms.py +188 -0
  159. package/md_cg/test_rejected_redact.py +146 -0
  160. package/md_cg/test_retr_s1b.py +2 -2
  161. package/md_cg/test_retr_s5.py +20 -7
  162. package/md_cg/test_review_cli_attribution.py +177 -0
  163. package/md_cg/test_review_cli_visibility.py +235 -0
  164. package/md_cg/test_review_conformance.py +21 -3
  165. package/md_cg/test_security_audit_v21.py +2 -2
  166. package/md_cg/test_server_version.py +67 -0
  167. package/md_cg/test_srcindex.py +171 -0
  168. package/md_cg/test_tenant_env_override_warn.py +63 -50
  169. package/md_cg/test_token_lowercase_form.py +315 -0
  170. package/md_cg/test_v21r1_package_version.py +310 -0
  171. package/md_cg/test_writepipe.py +10 -4
  172. package/md_cg/tokens.py +225 -95
  173. package/md_cg/tool_face.py +4 -4
  174. package/md_cg/trust.py +49 -5
  175. package/md_cg/units.py +204 -6
  176. package/md_cg/weights.py +4 -4
  177. package/md_cg/whitebox_kb/wisdom/knowledge_points.py +1 -1
  178. package/md_cg/writelimit.py +42 -8
  179. package/md_cg/writepipe.py +651 -554
  180. package/package.json +8 -2
  181. package/skills/plugin.json +1 -1
  182. package/src/bridge.ts +25 -2
  183. package/src/cli.ts +65 -0
  184. package/src/hooks.ts +229 -16
  185. package/src/init.ts +361 -0
  186. package/src/lib/prompt_safety.ts +88 -1
  187. package/zcode/AGENTS.md +10 -10
@@ -0,0 +1,301 @@
1
+ # -*- coding: utf-8 -*-
2
+ """md_cg · 常驻进程索引跨进程重载守卫(P1b-2 读面缓存代际,DSH 端在役复验)
3
+
4
+ 缺陷(修复前):`MdCG.__init__` 仅启动时 `self.index = self._load_index()`
5
+ 一次(mdcg.py),此后 read/get/search 全走内存态——其它进程(review_cli
6
+ autoflush=1)写盘对常驻进程不可见。复验证据链(dsh_restart_recheck_20260926,
7
+ 时间戳锁死):22:13:16 常驻进程装载快照(14135 节点) → 22:14:37 review_cli
8
+ 写入 → 22:14:43 磁盘 _index.json 已含探针 → 同常驻进程 read 仍 null、旁路
9
+ 新进程可读。
10
+
11
+ 修复:__init__ 记录 _index.json 签名 (st_mtime_ns, st_size);读路径入口
12
+ (get / search(基类与 MdCGOS)/ search_rrf(热缓存查询之前)/ list_goals)
13
+ 单点调 _maybe_reload_index()——签名变化才重载(一次 stat 微秒级);自身
14
+ flush 只追加 _index_log 分片不改 _index.json(签名不变正合适)、compact/
15
+ rebuild 写快照后主动刷签名(自写不自载);maintain action=reload 显式探活。
16
+
17
+ 守卫(六组,全部临时库 + 哑主密钥,绝不触真实 ~/.mdcg):
18
+ ① 红转绿(同解释器双实例,复刻 DSH 形态):A 建库常驻 → B 独立实例
19
+ add+close(compact 落快照=review_cli 退出同款)→ 盘面快照已含探针 →
20
+ A 禁用 reload 时 read null(修前病灶形态)→ 恢复后 read/search 命中
21
+ (修复生效)→ C 实例 forget+close → A read 归 null(tombstone 重放)。
22
+ ② 旁路新进程可读(subprocess 全新解释器 _load_index+read)——修前已绿,
23
+ 钉住「盘面账本正确」,防两层兜底(批次39 指纹自愈)未来被移除时复发。
24
+ ③ 自身写入零重载:A' 写入+flush+compact+rebuild 全程真实重载次数 0
25
+ (自写不自载循环防线);连续读 N 次=签名探测 N 次、重载 0 次。
26
+ ④ maintain reload 动作在位:MAINTAIN_ACTIONS 登记 + {"reloaded": bool}
27
+ 返回(他进程写入后 True 且读面可见;幂等二调 False)。
28
+ ⑤ goal=list 链路跨进程可见(list_goals 接线腿,active_goals 同源)。
29
+ ⑥ 性能粗测:无跨进程写入时接线态 vs 重载桩空转态逐读耗时对照(宽松
30
+ 上界断言防「重载被改成无条件」的性能回归;绝对数字只打印不作硬门)。
31
+
32
+ 如实边界:② 在修复前的 HEAD 上已绿(旁路进程本就走全新 _load_index);
33
+ 修复前红的正是 ① 的常驻读面——本守卫以「禁用 reload → null」复现该形态,
34
+ 并以 ③ 的计数桩钉住接线与自写不自载两道防线。
35
+
36
+ 运行:python -m md_cg.test_index_crossprocess_reload
37
+ """
38
+ from __future__ import annotations
39
+
40
+ import json
41
+ import os
42
+ import shutil
43
+ import subprocess
44
+ import sys
45
+ import tempfile
46
+ import time
47
+
48
+ from .mdcos import MdCGSecure
49
+ from .security import Principal
50
+
51
+ PASS = FAIL = 0
52
+ FAILS = []
53
+
54
+ REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
55
+
56
+ DUMMY_KEY = "ab" * 32 # 哑主密钥:本进程与子进程绝不触真实 ~/.mdcg
57
+
58
+ READ_CHILD = r'''
59
+ import sys
60
+ sys.path.insert(0, sys.argv[2])
61
+ from md_cg.mdcos import MdCGSecure
62
+ from md_cg.security import Principal
63
+ p = Principal(actor="p1b2-reader", clearance="internal", can_write=True,
64
+ can_admin=True, role="designer", auth_mode="test")
65
+ cg = MdCGSecure(sys.argv[1], principal=p)
66
+ try:
67
+ print("READ " + " ".join("1" if cg.get(nid) is not None else "0"
68
+ for nid in sys.argv[3].split(",")))
69
+ finally:
70
+ cg.close()
71
+ '''
72
+
73
+
74
+ def check(name, cond, detail=""):
75
+ global PASS, FAIL
76
+ if cond:
77
+ PASS += 1
78
+ print(f" [PASS] {name}" + (f" · {detail}" if detail else ""))
79
+ else:
80
+ FAIL += 1
81
+ FAILS.append(name)
82
+ print(f" [FAIL] {name} · {detail}")
83
+
84
+
85
+ def _principal(**kw):
86
+ d = dict(actor="p1b2", clearance="internal", can_write=True,
87
+ can_admin=True, role="designer", auth_mode="test")
88
+ d.update(kw)
89
+ return Principal(**d)
90
+
91
+
92
+ def _spawn_read(root, ids):
93
+ """独立子进程全新 _load_index + read(旁路读面)。"""
94
+ env = dict(os.environ)
95
+ env["MDCG_MASTER_KEY"] = DUMMY_KEY
96
+ env["PYTHONUTF8"] = "1"
97
+ env["PYTHONIOENCODING"] = "utf-8"
98
+ env["PYTHONPATH"] = REPO + os.pathsep + env.get("PYTHONPATH", "")
99
+ return subprocess.run(
100
+ [sys.executable, "-X", "utf8", "-c", READ_CHILD, root, REPO, ids],
101
+ capture_output=True, text=True, encoding="utf-8", errors="replace",
102
+ env=env, cwd=REPO)
103
+
104
+
105
+ def _snapshot_ids(root):
106
+ """盘面 _index.json 的节点 id 集(复验证据链 22:14:43 的观察点)。"""
107
+ p = os.path.join(root, "_index.json")
108
+ if not os.path.exists(p):
109
+ return None
110
+ with open(p, encoding="utf-8") as f:
111
+ return set(json.load(f).get("nodes") or {})
112
+
113
+
114
+ def _counting(cg):
115
+ """给 _maybe_reload_index 挂计数桩,返回 (计数 dict, 解挂函数)。"""
116
+ calls = {"probe": 0, "reload": 0}
117
+ orig = cg._maybe_reload_index
118
+
119
+ def counted():
120
+ calls["probe"] += 1
121
+ r = orig()
122
+ if r:
123
+ calls["reload"] += 1
124
+ return r
125
+
126
+ cg._maybe_reload_index = counted
127
+ return calls, (lambda: setattr(cg, "_maybe_reload_index", orig))
128
+
129
+
130
+ def main():
131
+ roots = []
132
+ # 纪律:哑主密钥先行——进程内任何 MdCGSecure 构造都不得触真实
133
+ # ~/.mdcg/master.key(_init_crypto 的 env 回落链,mdcos.py)。
134
+ os.environ["MDCG_MASTER_KEY"] = DUMMY_KEY
135
+ try:
136
+ # ---------- ① 红转绿:常驻 A + 独立写方 B(DSH 形态复刻) ----------
137
+ print("\n【①】常驻 A 建库 → 独立 B add+close(compact 落快照)→ 红转绿")
138
+ root1 = tempfile.mkdtemp(prefix="p1b2_1_")
139
+ roots.append(root1)
140
+ a = MdCGSecure(root1, principal=_principal())
141
+ a.add("seed_node_1", "# 功能名:种子节点\n# 正文:常驻 A 的初始记忆",
142
+ layer="knowledge")
143
+ a.flush() # 只追加 _index_log 分片,不动 _index.json
144
+ check("A 自身 flush 不改 _index.json 签名(分片追加不改快照)",
145
+ a._index_sig == a._index_signature())
146
+ b = MdCGSecure(root1, principal=_principal(actor="writer_b"))
147
+ b.add("reload_probe_1", "# 功能名:跨进程探针\n# 正文:B 写入的探针节点",
148
+ layer="knowledge")
149
+ b.close() # close → compact:写 _index.json(review_cli 退出同款)
150
+ ids = _snapshot_ids(root1)
151
+ check("盘面 _index.json 已含 B 的探针(复验证据链 22:14:43 形态)",
152
+ ids is not None and "reload_probe_1" in ids,
153
+ "snapshot_ids=%s" % (sorted(ids or [])[:8]))
154
+ # 红形态:修前无 _maybe_reload_index → 常驻 A 读不到
155
+ orig_reload = a._maybe_reload_index
156
+ a._maybe_reload_index = lambda: False
157
+ red = a.get("reload_probe_1")
158
+ a._maybe_reload_index = orig_reload
159
+ check("红(修前病灶形态):禁用 reload 时常驻 A read 探针 null",
160
+ red is None, repr(red)[:80])
161
+ fired = a._maybe_reload_index()
162
+ check("绿:_maybe_reload_index 签名变化触发重载", fired is True)
163
+ green = a.get("reload_probe_1")
164
+ check("绿:常驻 A read 探针命中", green is not None,
165
+ repr(green)[:80])
166
+ res, _meta = a.search("跨进程探针")
167
+ check("绿:常驻 A search(route/read 链路)命中探针",
168
+ any(n.get("id") == "reload_probe_1" for n, _s, _q in res),
169
+ str([n.get("id") for n, _s, _q in res])[:120])
170
+ # 删除可见性:C 实例 forget → tombstone 经重放对 A 生效
171
+ c = MdCGSecure(root1, principal=_principal(actor="writer_c"))
172
+ fr = c.forget("reload_probe_1", reason="P1b-2 tombstone 腿")
173
+ c.close()
174
+ check("C forget 成功(ok=True)", isinstance(fr, dict) and fr.get("ok"),
175
+ str(fr)[:80])
176
+ check("绿:常驻 A read 已删节点归 null(tombstone 重放)",
177
+ a.get("reload_probe_1") is None)
178
+ a.close()
179
+
180
+ # ---------- ② 旁路新进程可读(修前已绿,钉住盘面账本) ----------
181
+ print("\n【②】旁路全新子进程 read(_load_index 直读盘面)")
182
+ root2 = tempfile.mkdtemp(prefix="p1b2_2_")
183
+ roots.append(root2)
184
+ w = MdCGSecure(root2, principal=_principal(actor="writer_2"))
185
+ w.add("bypass_probe", "# 功能名:旁路探针\n# 正文:新进程应立即可见",
186
+ layer="knowledge")
187
+ w.close()
188
+ r2 = _spawn_read(root2, "bypass_probe,missing_node")
189
+ line = (r2.stdout or "").strip().splitlines()[-1] if (r2.stdout or "").strip() else ""
190
+ check("旁路新进程 read:探针 1 / 缺失 0(READ 1 0)",
191
+ line == "READ 1 0",
192
+ "stdout=%r stderr=%r" % ((r2.stdout or "")[:80], (r2.stderr or "")[:200]))
193
+
194
+ # ---------- ③ 自身写入零重载 + 自写不自载 ----------
195
+ print("\n【③】自身写入/flush/compact/rebuild 全程零真实重载")
196
+ root3 = tempfile.mkdtemp(prefix="p1b2_3_")
197
+ roots.append(root3)
198
+ a2 = MdCGSecure(root3, principal=_principal())
199
+ a2.add("own_seed", "# 功能名:自有种子\n# 正文:own seed", layer="knowledge")
200
+ a2.flush()
201
+ calls, uncount = _counting(a2)
202
+ for i in range(3):
203
+ a2.add("own_%d" % i, "# 功能名:自写%d\n# 正文:own write %d" % (i, i),
204
+ layer="knowledge")
205
+ a2.flush()
206
+ for i in range(3):
207
+ a2.get("own_%d" % i)
208
+ a2.search("自写")
209
+ a2.list_goals()
210
+ check("自身写入+flush+读全程:重载 0 次(写路径不接、flush 不改签名)",
211
+ calls["reload"] == 0, str(calls))
212
+ check("接线在位:读路径确经签名探测(3×get+1×search+1×list_goals=5 次)",
213
+ calls["probe"] >= 5, str(calls))
214
+ a2.compact_index()
215
+ check("compact 后签名已主动刷新(自写不自载前提)",
216
+ a2._index_sig == a2._index_signature())
217
+ a2.get("own_0")
218
+ a2.rebuild_index()
219
+ a2.get("own_0")
220
+ check("compact/rebuild 后紧接读:重载仍 0 次", calls["reload"] == 0,
221
+ str(calls))
222
+ uncount()
223
+ a2.close()
224
+
225
+ # ---------- ④ maintain reload 动作 ----------
226
+ print("\n【④】maintain action=reload 显式探活")
227
+ from .mdcos import MdCGOS
228
+ check('"reload" 已登记 MAINTAIN_ACTIONS', "reload" in MdCGOS.MAINTAIN_ACTIONS,
229
+ str(MdCGOS.MAINTAIN_ACTIONS))
230
+ a3 = MdCGSecure(root3, principal=_principal())
231
+ out0 = a3.maintain(action="reload")
232
+ check("幂等:签名未变时 reloaded=False",
233
+ isinstance(out0, dict) and out0.get("reloaded") is False,
234
+ str(out0)[:120])
235
+ bw = MdCGSecure(root3, principal=_principal(actor="writer_4"))
236
+ bw.add("maintain_probe", "# 功能名:维护探针\n# 正文:maintain reload 腿",
237
+ layer="knowledge")
238
+ bw.close()
239
+ out1 = a3.maintain(action="reload")
240
+ check("他进程写快照后 maintain reload 返回 reloaded=True",
241
+ isinstance(out1, dict) and out1.get("reloaded") is True,
242
+ str(out1)[:120])
243
+ check("探活后读面立即可见新节点", a3.get("maintain_probe") is not None)
244
+ a3.close()
245
+
246
+ # ---------- ⑤ goal=list 链路(list_goals 接线腿) ----------
247
+ print("\n【⑤】goal 跨进程可见(list_goals / active_goals)")
248
+ root4 = tempfile.mkdtemp(prefix="p1b2_4_")
249
+ roots.append(root4)
250
+ a4 = MdCGSecure(root4, principal=_principal())
251
+ check("常驻 A' 初始无目标", a4.list_goals() == [])
252
+ gw = MdCGSecure(root4, principal=_principal(actor="writer_5"))
253
+ gid = gw.add_goal("跨进程目标探针:P1b-2 list_goals 接线腿", priority=0.9)
254
+ gw.close()
255
+ gl = a4.list_goals()
256
+ check("goal=list 经 list_goals 重载后可见",
257
+ any(g.get("id") == gid for g in gl),
258
+ str([g.get("id") for g in gl])[:120])
259
+ ag = a4.active_goals()
260
+ check("active_goals 同源可见",
261
+ any(g.get("id") == gid for g in ag))
262
+ a4.close()
263
+
264
+ # ---------- ⑥ 性能粗测:无跨进程写入时逐读开销 ----------
265
+ print("\n【⑥】性能粗测(无跨进程写入,接线态 vs 重载桩空转态)")
266
+ N = 300
267
+ a2c = MdCGSecure(root3, principal=_principal())
268
+ for _ in range(5):
269
+ a2c.get("own_0")
270
+ t0 = time.perf_counter()
271
+ for _ in range(N):
272
+ a2c.get("own_0")
273
+ t_on = (time.perf_counter() - t0) / N * 1e6
274
+ orig = a2c._maybe_reload_index
275
+ a2c._maybe_reload_index = lambda: False
276
+ for _ in range(5):
277
+ a2c.get("own_0")
278
+ t0 = time.perf_counter()
279
+ for _ in range(N):
280
+ a2c.get("own_0")
281
+ t_off = (time.perf_counter() - t0) / N * 1e6
282
+ a2c._maybe_reload_index = orig
283
+ a2c.close()
284
+ print(" 接线态 %.1f µs/读 · 桩空转态 %.1f µs/读 · 增幅 %.1f%%"
285
+ % (t_on, t_off, (t_on - t_off) / max(t_off, 1e-9) * 100))
286
+ check("性能上界:接线态 ≤ 桩空转态×3 + 30µs(stat 微秒级;宽松防抖)",
287
+ t_on <= t_off * 3 + 30.0,
288
+ "on=%.1f off=%.1f" % (t_on, t_off))
289
+ finally:
290
+ for r_ in roots:
291
+ shutil.rmtree(r_, ignore_errors=True)
292
+
293
+ print("\n" + "=" * 64)
294
+ print(f"PASS={PASS} FAIL={FAIL}")
295
+ if FAILS:
296
+ print("失败项:" + ";".join(FAILS))
297
+ return 0 if FAIL == 0 else 1
298
+
299
+
300
+ if __name__ == "__main__":
301
+ sys.exit(main())