@furongjun1999/dsh-memory 0.6.0 → 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 (170) hide show
  1. package/README.md +588 -572
  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/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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  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_v25.md +230 -0
  23. 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
  24. 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
  25. package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +12 -0
  26. 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
  27. package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +21 -9
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. package/lib/bridge.js +24 -2
  35. package/lib/hooks.d.ts +31 -2
  36. package/lib/hooks.js +219 -16
  37. package/lib/init.js +16 -1
  38. package/lib/lib/prompt_safety.d.ts +52 -1
  39. package/lib/lib/prompt_safety.js +76 -1
  40. package/md_cg/audit.py +652 -379
  41. package/md_cg/bench6_arms.py +1 -1
  42. package/md_cg/bench_p0.py +1 -1
  43. package/md_cg/branches.py +2 -2
  44. package/md_cg/ccgc.py +1071 -1006
  45. package/md_cg/chain.py +1 -1
  46. package/md_cg/consistency.py +97 -9
  47. package/md_cg/consolidate.py +34 -9
  48. package/md_cg/crypto.py +59 -26
  49. package/md_cg/docindex.py +16 -1
  50. package/md_cg/evolution.py +17 -1
  51. package/md_cg/export.py +1 -1
  52. package/md_cg/forgetting.py +309 -13
  53. package/md_cg/fsutil.py +454 -8
  54. package/md_cg/identity.py +3 -3
  55. package/md_cg/insight.py +24 -3
  56. package/md_cg/linkref.py +1 -1
  57. package/md_cg/logref.py +327 -0
  58. package/md_cg/mcp_server.py +4152 -3818
  59. package/md_cg/mdcg.py +4208 -3547
  60. package/md_cg/mdcos.py +4491 -4159
  61. package/md_cg/mreview/pipeline.py +15 -1
  62. package/md_cg/nodefile.py +639 -575
  63. package/md_cg/protect.py +158 -11
  64. package/md_cg/protocol.py +41 -2
  65. package/md_cg/provenance.py +22 -4
  66. package/md_cg/reach.py +4 -4
  67. package/md_cg/readcache.py +76 -15
  68. package/md_cg/reconcile.py +1 -1
  69. package/md_cg/refindex.py +246 -37
  70. package/md_cg/refine.py +1 -1
  71. package/md_cg/routing.py +32 -5
  72. package/md_cg/run_tests.py +17 -0
  73. package/md_cg/scrub.py +30 -3
  74. package/md_cg/security.py +47 -1
  75. package/md_cg/self_state.py +5 -5
  76. package/md_cg/sources.py +23 -6
  77. package/md_cg/srcindex.py +352 -0
  78. package/md_cg/stg.py +47 -3
  79. package/md_cg/subgraph.py +2 -2
  80. package/md_cg/sustain.py +1299 -1168
  81. package/md_cg/tasks.py +469 -470
  82. package/md_cg/test_b1_auto_id_multiproc.py +277 -0
  83. package/md_cg/test_b1b2_write_face.py +417 -0
  84. package/md_cg/test_b2_sensitivity_landing.py +223 -0
  85. package/md_cg/test_b3_merge_keeps_content.py +576 -0
  86. package/md_cg/test_b4_shard_dir_selfheal.py +843 -0
  87. package/md_cg/test_b4_shard_dir_selfheal_guard.py +238 -0
  88. package/md_cg/test_c3_transient_read_negative.py +793 -0
  89. package/md_cg/test_c8_search_rrf_gates.py +502 -0
  90. package/md_cg/test_ccg_form_parity.py +188 -0
  91. package/md_cg/test_ccgc.py +28 -3
  92. package/md_cg/test_govern_directread.py +17 -4
  93. package/md_cg/test_h2_session_view_norm.py +233 -0
  94. package/md_cg/test_h4_sustain_snapshot.py +1377 -0
  95. package/md_cg/test_hive_ingest.py +285 -285
  96. package/md_cg/test_issue39_utf8_stdio.py +307 -16
  97. package/md_cg/test_issue43_default_policy.py +865 -0
  98. package/md_cg/test_legacy_p3_node_id_type.py +375 -0
  99. package/md_cg/test_linkref.py +10 -3
  100. package/md_cg/test_lock.py +2 -2
  101. package/md_cg/test_logref.py +1109 -0
  102. package/md_cg/test_m3_h9_bucket_health_protect_mark.py +301 -0
  103. package/md_cg/test_m3_h9_semantic_guard.py +525 -0
  104. package/md_cg/test_mr_m2.py +15 -3
  105. package/md_cg/test_n130_verify_falsified_protect.py +1 -1
  106. package/md_cg/test_n139_dek_provision_failclosed.py +185 -0
  107. package/md_cg/test_n176_link_trust.py +221 -0
  108. package/md_cg/test_n178_units_jobid_gate.py +212 -0
  109. package/md_cg/test_n184_keys_concurrent_provision.py +307 -0
  110. package/md_cg/test_n195_writepath_reload.py +283 -0
  111. package/md_cg/test_n196_stale_gate_skip.py +169 -0
  112. package/md_cg/test_n197_n208_write_face_gates.py +471 -0
  113. package/md_cg/test_n198_tokens_corrupt_failclosed.py +225 -0
  114. package/md_cg/test_n199_tokens_concurrent_write.py +378 -0
  115. package/md_cg/test_n201_proposal_visibility.py +382 -0
  116. package/md_cg/test_n202_session_notes_visibility.py +461 -0
  117. package/md_cg/test_n204_n205_n226_n227_n228_n229_exit_gates.py +542 -0
  118. package/md_cg/test_n206_stdio_jsonrpc_type.py +403 -0
  119. package/md_cg/test_n209_verify_write_face_gates.py +461 -0
  120. package/md_cg/test_n212_n213_n224_generation_gates.py +513 -0
  121. package/md_cg/test_n214_n215_n221_n222_write_face_gates.py +563 -0
  122. package/md_cg/test_n225_nonobject_load.py +1110 -0
  123. package/md_cg/test_n62_tenant_bind_failclosed.py +200 -0
  124. package/md_cg/test_neg_condition_hits.py +333 -0
  125. package/md_cg/test_neg_tail_honesty.py +663 -0
  126. package/md_cg/test_none_id_write_guard.py +4 -3
  127. package/md_cg/test_opt_batch1_md_cg.py +451 -0
  128. package/md_cg/test_p1.py +5 -5
  129. package/md_cg/test_p11_consistency.py +161 -12
  130. package/md_cg/test_p26_refindex.py +2 -2
  131. package/md_cg/test_p27_docindex.py +777 -774
  132. package/md_cg/test_p28_refcheck.py +740 -13
  133. package/md_cg/test_p29_session_ingest_export.py +2 -2
  134. package/md_cg/test_p2_mcp.py +12 -1
  135. package/md_cg/test_p30_maintain.py +33 -2
  136. package/md_cg/test_p31_insight.py +1 -0
  137. package/md_cg/test_p9c_dedup_hints.py +276 -0
  138. package/md_cg/test_policy_required_ccg.py +426 -0
  139. package/md_cg/test_protocol.py +22 -0
  140. package/md_cg/test_rank_parity_score_mode.py +516 -0
  141. package/md_cg/test_read_face_input_gates.py +363 -0
  142. package/md_cg/test_read_face_semantics.py +489 -0
  143. package/md_cg/test_recall_face_guards.py +812 -0
  144. package/md_cg/test_rejected_credential_forms.py +188 -0
  145. package/md_cg/test_rejected_redact.py +146 -0
  146. package/md_cg/test_retr_s1b.py +2 -2
  147. package/md_cg/test_retr_s5.py +20 -7
  148. package/md_cg/test_review_conformance.py +21 -3
  149. package/md_cg/test_security_audit_v21.py +2 -2
  150. package/md_cg/test_server_version.py +67 -0
  151. package/md_cg/test_srcindex.py +171 -0
  152. package/md_cg/test_tenant_env_override_warn.py +63 -50
  153. package/md_cg/test_token_lowercase_form.py +315 -0
  154. package/md_cg/test_v21r1_package_version.py +310 -0
  155. package/md_cg/test_writepipe.py +10 -4
  156. package/md_cg/tokens.py +225 -95
  157. package/md_cg/tool_face.py +4 -4
  158. package/md_cg/trust.py +49 -5
  159. package/md_cg/units.py +204 -6
  160. package/md_cg/weights.py +4 -4
  161. package/md_cg/whitebox_kb/wisdom/knowledge_points.py +1 -1
  162. package/md_cg/writelimit.py +42 -8
  163. package/md_cg/writepipe.py +651 -554
  164. package/package.json +3 -2
  165. package/skills/plugin.json +1 -1
  166. package/src/bridge.ts +25 -2
  167. package/src/hooks.ts +229 -16
  168. package/src/init.ts +15 -1
  169. package/src/lib/prompt_safety.ts +88 -1
  170. package/zcode/AGENTS.md +10 -10
@@ -0,0 +1,277 @@
1
+ # -*- coding: utf-8 -*-
2
+ # 功能名:B1 自动节点 id 唯一性的「多进程写面」守卫(N 进程 × M 条自动写入不传 node_id)
3
+ # 生效条件:md_cg/writepipe.WritePipeline.execute 与 md_cg.mcp_server._dispatch 的
4
+ # mdcg_remember 分支均**委托** md_cg.mdcg.mint_auto_id 铸造自动 id 时
5
+ # (判据面:两处都是 `a.get("node_id") or mint_auto_id(cg)`,无一处在
6
+ # 本文件之外自铸 `mem_<毫秒>`);
7
+ # 沙箱条件:MDCG_ROOT / MDCG_AUX_ROOT / MDCG_MASTER_KEY / MDCG_POLICY_FILE
8
+ # 全部指向 tempfile.mkdtemp 建的独立目录,绝不触在役库。
9
+ # 子功能:① 真实时钟 N 进程并发写(pipe 面与 mdcg_remember 面混用同一库根)——
10
+ # 回执 id 唯一数 == 总笔数,且逐笔正文按 id 读回命中;
11
+ # ② 同一毫秒(固定毫秒替身)N 进程并发写——缺陷触发条件;
12
+ # ③ 显式 node_id 的「同 id 即改写」覆写语义原样保留(不得被自动 id 闸误伤)。
13
+ # 执行:python -X utf8 -m md_cg.test_b1_auto_id_multiproc
14
+ # (cwd = 仓库根;不带参数、无网络、无外部依赖,全程沙箱)
15
+ # 验证方式:本文件自跑(自带断言计数与退出码 rc=0/1);定点变异自证见文件尾
16
+ # `--drop-fix`(抽掉 mint_auto_id 的随机段 → 本守卫必红,恢复即绿)。
17
+ # 不适用条件:情感交互|闲聊|纯查询无改动;
18
+ # 跨进程「存在性闸」的原子性不在本守卫面内(各进程持各自索引快照,
19
+ # 跨进程唯一性主要靠随机段熵——见 mint_auto_id 注释的边界声明)。
20
+ """B1 守卫:自动 id 在多进程写面下的唯一性与可检索性。
21
+
22
+ 缺陷(修复前,本守卫的定点变异--drop-fix 现场实抓):
23
+ 自动 id 曾在两处**各写一份** `"mem_" + 毫秒时间戳`(writepipe.execute 与
24
+ mcp_server.mdcg_remember)。同一毫秒内自动写入铸出同一 id → `add` 的 upsert
25
+ 语义把后一笔**静默顶替**前一笔:回执 committed=true,而正文全库 0 命中。
26
+
27
+ 本守卫的判别力锚点(每条断言都可定点变异):
28
+ A 组 真实时钟并发:唯一 id 数 == 总笔数(N*M);逐笔正文按 id 读回命中。
29
+ B 组 同毫秒并发:把子进程的时钟钉在**同一毫秒**(缺陷现场条件),上述两条仍须成立。
30
+ C 组 显式 node_id:同 id 二次写入 = 改写(索引单条目、正文为第二次、id 未被换)。
31
+
32
+ 沙箱(硬约束):一切读写都在 tempfile.mkdtemp 内——aux(密钥/身份)、各段库根、
33
+ 策略文件、子进程 env 全部指到沙箱;跑完 rmtree。**绝不碰在役库与在役 serve。**
34
+ """
35
+ import json
36
+ import os
37
+ import shutil
38
+ import subprocess
39
+ import sys
40
+ import tempfile
41
+
42
+ # 沙箱必须在**任何** md_cg 子模块 import 之前设好:`crypto.MASTER_FILE` 在模块
43
+ # 导入时求值(`aux_root()`),晚设会让密钥面指回 ~/.mcg。
44
+ _SANDBOX = tempfile.mkdtemp(prefix="b1mp_sandbox_")
45
+ os.environ["MDCG_AUX_ROOT"] = _SANDBOX
46
+ os.environ["MDCG_ROOT"] = os.path.join(_SANDBOX, "root")
47
+ os.environ["MDCG_MASTER_KEY"] = os.urandom(32).hex()
48
+ _POLICY = os.path.join(_SANDBOX, "policy.json")
49
+ with open(_POLICY, "w", encoding="utf-8") as _f: # audit 闸 ACCEPT 面:
50
+ json.dump({"forbidden": [], "required": ["B1PROBE"]}, _f) # required 命中
51
+ os.environ["MDCG_POLICY_FILE"] = _POLICY
52
+ os.environ.pop("MDCG_TEST_LIVE_ROOT", None)
53
+
54
+ from . import writepipe # noqa: E402
55
+ from .mdcos import MdCGSecure # noqa: E402
56
+ from .security import Principal # noqa: E402
57
+
58
+ _REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
59
+ _KEK = os.urandom(32)
60
+ _KEK_FILE = os.path.join(_SANDBOX, "kek.hex")
61
+ with open(_KEK_FILE, "w", encoding="utf-8") as _f:
62
+ _f.write(_KEK.hex())
63
+
64
+ MIXED_MS = 1790743939104 # 固定毫秒(同毫秒 = 缺陷触发条件)
65
+ N_PROCS = 4
66
+ M_EACH = 6
67
+
68
+ _OK = 0
69
+ _BAD = []
70
+
71
+
72
+ def _check(name, cond, detail=""):
73
+ global _OK
74
+ if cond:
75
+ _OK += 1
76
+ print(" ok %s" % name)
77
+ else:
78
+ _BAD.append(name)
79
+ print(" FAIL %s %s" % (name, detail))
80
+
81
+
82
+ # 子进程 worker:独立 python 进程,用与主进程同一沙箱 env + 显式 argv。
83
+ _WORKER = os.path.join(_SANDBOX, "_b1mp_worker.py")
84
+ _WORKER_SRC = '''# -*- coding: utf-8 -*-
85
+ import json, os, sys
86
+ repo, root, kek_hex, idx, count, face, ms_arg = sys.argv[1:8]
87
+ idx, count = int(idx), int(count)
88
+ sys.path.insert(0, repo)
89
+ import time as _time
90
+ import md_cg.mdcg as mdcg
91
+ if ms_arg != "-":
92
+ class _FakeTime(object):
93
+ def __init__(self, ts): self._ts = ts
94
+ def time(self): return self._ts
95
+ def __getattr__(self, k): return getattr(_time, k)
96
+ mdcg.time = _FakeTime(float(ms_arg) / 1000.0)
97
+ from md_cg import mcp_server, writepipe
98
+ from md_cg.mdcos import MdCGSecure
99
+ from md_cg.security import Principal
100
+ p = Principal(tenant="t1", actor="alice", role="designer", clearance="secret",
101
+ can_write=True, can_admin=True)
102
+ cg = MdCGSecure(root, principal=p, master_key=bytes.fromhex(kek_hex))
103
+ recs = []
104
+ for i in range(count):
105
+ mark = "B1PROBE pid=%d idx=%d no=%d" % (os.getpid(), idx, i)
106
+ out, err = None, None
107
+ try:
108
+ if face == "pipe":
109
+ out = writepipe.default_pipeline().execute(
110
+ cg, {"content_kind": "text", "content": mark})
111
+ else:
112
+ out = mcp_server._dispatch(cg, "mdcg_remember",
113
+ {"content": mark, "content_kind": "text"})
114
+ except Exception as exc:
115
+ err = "%s: %s" % (type(exc).__name__, str(exc)[:160])
116
+ recs.append({"mark": mark, "id": (out or {}).get("id"),
117
+ "ok": (out or {}).get("ok"),
118
+ "committed": (out or {}).get("committed"), "err": err})
119
+ cg.close()
120
+ sys.stdout.write(json.dumps({"idx": idx, "recs": recs}, ensure_ascii=False) + "\\n")
121
+ '''
122
+
123
+
124
+ def _write_worker():
125
+ with open(_WORKER, "w", encoding="utf-8", newline="\n") as f:
126
+ f.write(_WORKER_SRC)
127
+
128
+
129
+ def _mk_secure(tag):
130
+ root = os.path.join(_SANDBOX, "lib_" + tag)
131
+ p = Principal(tenant="t1", actor="alice", role="designer",
132
+ clearance="secret", can_write=True, can_admin=True)
133
+ return MdCGSecure(root, principal=p, master_key=_KEK), root
134
+
135
+
136
+ def _spawn(root, n, m, face, ms):
137
+ """起 n 个独立进程各写 m 条,返回全部回执记录。"""
138
+ procs = []
139
+ for i in range(n):
140
+ f = ("pipe" if i % 2 == 0 else "mcp") if face == "mix" else face
141
+ procs.append(subprocess.Popen(
142
+ [sys.executable, "-X", "utf8", _WORKER, _REPO, root, _KEK.hex(),
143
+ str(i), str(m), f, ms],
144
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,
145
+ encoding="utf-8", errors="replace", env=dict(os.environ),
146
+ shell=False))
147
+ recs, rcs = [], []
148
+ for pr in procs:
149
+ out, err = pr.communicate()
150
+ rcs.append(pr.returncode)
151
+ for line in out.splitlines():
152
+ if line.strip().startswith("{"):
153
+ recs.extend(json.loads(line)["recs"])
154
+ if pr.returncode != 0:
155
+ print(" [worker rc=%d] %s" % (pr.returncode, err.strip()[-200:]))
156
+ return recs, rcs
157
+
158
+
159
+ def _readback(root, recs):
160
+ """按回执 id 逐笔读回(新实例 = 从盘上重载索引),返回 (miss, mism, n_index)。"""
161
+ cg = MdCGSecure(root, principal=Principal(
162
+ tenant="t1", actor="alice", role="designer", clearance="secret",
163
+ can_write=True, can_admin=True), master_key=_KEK)
164
+ miss, mism = [], []
165
+ for r in recs:
166
+ nid = r.get("id")
167
+ node = cg.get(nid) if nid else None
168
+ body = (node or {}).get("content") or ""
169
+ if not node:
170
+ miss.append((r["mark"], nid))
171
+ elif r["mark"] not in body:
172
+ mism.append((r["mark"], nid, body[:50]))
173
+ n_index = len(cg.index["nodes"])
174
+ cg.close()
175
+ return miss, mism, n_index
176
+
177
+
178
+ def _one_segment(tag, n, m, face, ms):
179
+ """一段并发写 + 三条读数。返回 (recs, miss, mism, n_index, rcs)。"""
180
+ cg_pre, root = _mk_secure(tag) # 预热:先把 DEK/身份落定,避免 N 进程竞态
181
+ cg_pre.close()
182
+ recs, rcs = _spawn(root, n, m, face, ms)
183
+ miss, mism, n_index = _readback(root, recs)
184
+ return recs, miss, mism, n_index, rcs
185
+
186
+
187
+ def main():
188
+ _write_worker()
189
+ print("\n[B1-A] 真实时钟 · %d 进程 × %d 条 · 混合写面(pipe / mdcg_remember)"
190
+ % (N_PROCS, M_EACH))
191
+ recs, miss, mism, n_index, rcs = _one_segment(
192
+ "A", N_PROCS, M_EACH, "mix", "-")
193
+ total = len(recs)
194
+ ids = [r["id"] for r in recs if r.get("id")]
195
+ uniq = len(set(ids))
196
+ errs = [r for r in recs if r.get("err")]
197
+ noid = [r for r in recs if not r.get("err") and not r.get("id")]
198
+ notok = [r for r in recs if not r.get("err") and r.get("ok") is not True]
199
+ print(" total=%d with_id=%d uniq=%d errs=%d no_id=%d not_ok=%d "
200
+ "miss=%d mism=%d index=%d rc=%s"
201
+ % (total, len(ids), uniq, len(errs), len(noid), len(notok),
202
+ len(miss), len(mism), n_index, rcs))
203
+ if errs:
204
+ print(" first err:", errs[0]["err"])
205
+
206
+ _check("B1-A1 每笔都拿到 ok 回执与 id(零异常/零未回执)",
207
+ total == N_PROCS * M_EACH and not errs and not noid and not notok,
208
+ "total=%d errs=%d no_id=%d not_ok=%d %s"
209
+ % (total, len(errs), len(noid), len(notok),
210
+ (errs or noid or notok)[:1]))
211
+ _check("B1-A2 唯一 id 数 == 总笔数", uniq == total and total > 0,
212
+ "uniq=%d total=%d" % (uniq, total))
213
+ _check("B1-A3 每笔正文都能按 id 读回(无「已回执却 0 命中」)",
214
+ total > 0 and not miss and not mism, "miss=%d mism=%d %s"
215
+ % (len(miss), len(mism), (miss + mism)[:2]))
216
+ _check("B1-A4 库内节点数 == 总笔数(回执与盘面对账)",
217
+ total > 0 and n_index == total, "index=%d total=%d" % (n_index, total))
218
+
219
+ print("\n[B1-B] 同毫秒(固定毫秒 %d)· %d 进程 × %d 条 · pipe 面"
220
+ % (MIXED_MS, N_PROCS, M_EACH))
221
+ recs_b, miss_b, mism_b, n_index_b, rcs_b = _one_segment(
222
+ "B", N_PROCS, M_EACH, "pipe", str(MIXED_MS))
223
+ total_b = len(recs_b)
224
+ ids_b = [r["id"] for r in recs_b if r.get("id")]
225
+ uniq_b = len(set(ids_b))
226
+ errs_b = [r for r in recs_b if r.get("err")]
227
+ print(" total=%d with_id=%d uniq=%d errs=%d miss=%d mism=%d index=%d rc=%s"
228
+ % (total_b, len(ids_b), uniq_b, len(errs_b), len(miss_b),
229
+ len(mism_b), n_index_b, rcs_b))
230
+ if errs_b:
231
+ print(" first err:", errs_b[0]["err"])
232
+ _check("B1-B1 同毫秒下每笔都拿到 ok 回执与 id",
233
+ total_b == N_PROCS * M_EACH and not errs_b
234
+ and all(r.get("ok") is True and r.get("id") for r in recs_b),
235
+ "total=%d errs=%d with_id=%d" % (total_b, len(errs_b), len(ids_b)))
236
+ _check("B1-B2 同毫秒下唯一 id 数 == 总笔数",
237
+ uniq_b == total_b and total_b > 0,
238
+ "uniq=%d total=%d ids=%s" % (uniq_b, total_b, ids_b[:3]))
239
+ _check("B1-B3 同毫秒下每笔正文都能按 id 读回",
240
+ total_b > 0 and not miss_b and not mism_b, "miss=%d mism=%d %s"
241
+ % (len(miss_b), len(mism_b), (miss_b + mism_b)[:2]))
242
+
243
+ print("\n[B1-C] 显式 node_id 的覆写语义(同 id 二次写入是改写)")
244
+ cg_c, _root_c = _mk_secure("C")
245
+ pipe = writepipe.default_pipeline()
246
+ eid = "b1mp_explicit_upsert"
247
+ o1 = pipe.execute(cg_c, {"node_id": eid, "content_kind": "text",
248
+ "content": "B1PROBE 显式 id 第一次"})
249
+ o2 = pipe.execute(cg_c, {"node_id": eid, "content_kind": "text",
250
+ "content": "B1PROBE 显式 id 第二次改写"})
251
+ node_c = cg_c.get(eid) or {}
252
+ body_c = node_c.get("content") or ""
253
+ same_key = [k for k in cg_c.index["nodes"] if k == eid]
254
+ auto_keys = [k for k in cg_c.index["nodes"] if k.startswith("mem_")]
255
+ _check("B1-C1 显式 id 两次返回同一 id(未被换成自动 id)",
256
+ o1.get("id") == o2.get("id") == eid,
257
+ "%r %r" % (o1.get("id"), o2.get("id")))
258
+ _check("B1-C2 索引里该 id 唯一(是改写不是新建)",
259
+ len(same_key) == 1, "命中 %d 个" % len(same_key))
260
+ _check("B1-C3 正文为第二次(覆写生效)",
261
+ "第二次改写" in body_c and "第一次" not in body_c, body_c[:50])
262
+ _check("B1-C4 显式 id 未产生自动 id 旁路节点",
263
+ not auto_keys and cg_c.index["nodes"].get(eid) is not None,
264
+ "auto_keys=%s" % auto_keys)
265
+ cg_c.close()
266
+
267
+ print("\n==== B1 多进程写面守卫:%d 通过%s ====" % (
268
+ _OK, ",%d 失败:%s" % (len(_BAD), "; ".join(_BAD)) if _BAD else ""))
269
+ return 1 if _BAD else 0
270
+
271
+
272
+ if __name__ == "__main__":
273
+ try:
274
+ _rc = main()
275
+ finally:
276
+ shutil.rmtree(_SANDBOX, ignore_errors=True)
277
+ sys.exit(_rc)