@furongjun1999/dsh-memory 0.6.0 → 0.7.0

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 (202) hide show
  1. package/README.md +618 -572
  2. package/codebuddy/CODEBUDDY.md +10 -10
  3. package/data/policy.json +27 -0
  4. package/docs/README.md +1 -0
  5. package/docs/discipline/harnesses.yaml +31 -0
  6. package/docs/discipline/templates/full.md.tmpl +1 -1
  7. package/docs/discipline/templates/rules.mdc.tmpl +1 -1
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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 +460 -0
  15. 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
  16. 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 +97 -0
  17. 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 +458 -0
  18. 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
  19. 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
  20. 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
  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_v20.md +283 -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_v21.md +224 -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_v22.md +223 -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_v23.md +293 -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_v24.md +303 -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_v25.md +230 -0
  27. package/docs/hive//346/243/200/347/264/242/347/256/227/346/263/225/345/217/243/345/276/204/345/257/271/347/205/247_v0.1.md +136 -11
  28. 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 +52 -2
  29. 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
  30. package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +100 -0
  31. 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
  32. package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +57 -9
  33. 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
  34. 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 +183 -0
  35. 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
  36. 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
  37. 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
  38. 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 +547 -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/lib/bridge.js +24 -2
  42. package/lib/hooks.d.ts +31 -2
  43. package/lib/hooks.js +219 -16
  44. package/lib/init.js +16 -1
  45. package/lib/lib/prompt_safety.d.ts +52 -1
  46. package/lib/lib/prompt_safety.js +76 -1
  47. package/md_cg/audit.py +652 -379
  48. package/md_cg/bench6_arms.py +1 -1
  49. package/md_cg/bench_e2e_locomo_qa.py +11 -4
  50. package/md_cg/bench_p0.py +1 -1
  51. package/md_cg/branches.py +2 -2
  52. package/md_cg/ccgc.py +1071 -1006
  53. package/md_cg/chain.py +48 -1
  54. package/md_cg/consistency.py +97 -9
  55. package/md_cg/consolidate.py +34 -9
  56. package/md_cg/crypto.py +59 -26
  57. package/md_cg/docindex.py +16 -1
  58. package/md_cg/evolution.py +17 -1
  59. package/md_cg/export.py +1 -1
  60. package/md_cg/forgetting.py +309 -13
  61. package/md_cg/freshness.py +511 -0
  62. package/md_cg/fsutil.py +454 -8
  63. package/md_cg/generation.py +409 -0
  64. package/md_cg/hotcache.py +4 -1
  65. package/md_cg/identity.py +3 -3
  66. package/md_cg/insight.py +24 -3
  67. package/md_cg/linkref.py +1 -1
  68. package/md_cg/logref.py +327 -0
  69. package/md_cg/mcp_server.py +4261 -3818
  70. package/md_cg/mdcg.py +4695 -3547
  71. package/md_cg/mdcos.py +4672 -4159
  72. package/md_cg/mreview/pipeline.py +15 -1
  73. package/md_cg/nodefile.py +713 -576
  74. package/md_cg/protect.py +158 -11
  75. package/md_cg/protocol.py +41 -2
  76. package/md_cg/provenance.py +22 -4
  77. package/md_cg/reach.py +4 -4
  78. package/md_cg/readcache.py +76 -15
  79. package/md_cg/reconcile.py +1 -1
  80. package/md_cg/refindex.py +246 -37
  81. package/md_cg/refine.py +1 -1
  82. package/md_cg/routing.py +32 -5
  83. package/md_cg/run_tests.py +17 -0
  84. package/md_cg/scrub.py +30 -3
  85. package/md_cg/security.py +47 -1
  86. package/md_cg/self_state.py +5 -5
  87. package/md_cg/semantic/canonical.py +22 -0
  88. package/md_cg/semantic/unify.py +73 -22
  89. package/md_cg/semantic/unify_fixture.json +25 -0
  90. package/md_cg/sleep.py +1297 -0
  91. package/md_cg/sources.py +23 -6
  92. package/md_cg/srcindex.py +352 -0
  93. package/md_cg/stg.py +47 -3
  94. package/md_cg/subgraph.py +2 -2
  95. package/md_cg/sustain.py +1436 -1168
  96. package/md_cg/tasks.py +469 -470
  97. package/md_cg/test_auto_defaults.py +424 -0
  98. package/md_cg/test_b1_auto_id_multiproc.py +277 -0
  99. package/md_cg/test_b1b2_write_face.py +417 -0
  100. package/md_cg/test_b2_sensitivity_landing.py +223 -0
  101. package/md_cg/test_b3_merge_keeps_content.py +576 -0
  102. package/md_cg/test_b4_shard_dir_selfheal.py +843 -0
  103. package/md_cg/test_b4_shard_dir_selfheal_guard.py +238 -0
  104. package/md_cg/test_boundary_hit.py +410 -0
  105. package/md_cg/test_c3_transient_read_negative.py +793 -0
  106. package/md_cg/test_c8_search_rrf_gates.py +502 -0
  107. package/md_cg/test_ccg_form_parity.py +188 -0
  108. package/md_cg/test_ccgc.py +28 -3
  109. package/md_cg/test_en_pipeline.py +22 -13
  110. package/md_cg/test_generation_guard.py +352 -0
  111. package/md_cg/test_govern_directread.py +17 -4
  112. package/md_cg/test_h2_session_view_norm.py +233 -0
  113. package/md_cg/test_h4_sustain_snapshot.py +1387 -0
  114. package/md_cg/test_hive_ingest.py +285 -285
  115. package/md_cg/test_issue39_utf8_stdio.py +307 -16
  116. package/md_cg/test_issue43_default_policy.py +865 -0
  117. package/md_cg/test_legacy_p3_node_id_type.py +375 -0
  118. package/md_cg/test_linkref.py +10 -3
  119. package/md_cg/test_lock.py +2 -2
  120. package/md_cg/test_logref.py +1109 -0
  121. package/md_cg/test_m3_h9_bucket_health_protect_mark.py +301 -0
  122. package/md_cg/test_m3_h9_semantic_guard.py +525 -0
  123. package/md_cg/test_mr_m2.py +15 -3
  124. package/md_cg/test_n130_verify_falsified_protect.py +1 -1
  125. package/md_cg/test_n139_dek_provision_failclosed.py +185 -0
  126. package/md_cg/test_n176_link_trust.py +221 -0
  127. package/md_cg/test_n178_units_jobid_gate.py +212 -0
  128. package/md_cg/test_n184_keys_concurrent_provision.py +307 -0
  129. package/md_cg/test_n195_writepath_reload.py +283 -0
  130. package/md_cg/test_n196_stale_gate_skip.py +169 -0
  131. package/md_cg/test_n197_n208_write_face_gates.py +471 -0
  132. package/md_cg/test_n198_tokens_corrupt_failclosed.py +225 -0
  133. package/md_cg/test_n199_tokens_concurrent_write.py +378 -0
  134. package/md_cg/test_n201_proposal_visibility.py +382 -0
  135. package/md_cg/test_n202_session_notes_visibility.py +461 -0
  136. package/md_cg/test_n204_n205_n226_n227_n228_n229_exit_gates.py +542 -0
  137. package/md_cg/test_n206_stdio_jsonrpc_type.py +403 -0
  138. package/md_cg/test_n209_verify_write_face_gates.py +461 -0
  139. package/md_cg/test_n212_n213_n224_generation_gates.py +519 -0
  140. package/md_cg/test_n214_n215_n221_n222_write_face_gates.py +563 -0
  141. package/md_cg/test_n225_nonobject_load.py +1110 -0
  142. package/md_cg/test_n230_dirty_replay.py +375 -0
  143. package/md_cg/test_n62_tenant_bind_failclosed.py +200 -0
  144. package/md_cg/test_neg_condition_hits.py +333 -0
  145. package/md_cg/test_neg_tail_honesty.py +663 -0
  146. package/md_cg/test_none_id_write_guard.py +4 -3
  147. package/md_cg/test_opt_batch1_md_cg.py +451 -0
  148. package/md_cg/test_p1.py +5 -5
  149. package/md_cg/test_p11_consistency.py +161 -12
  150. package/md_cg/test_p26_refindex.py +2 -2
  151. package/md_cg/test_p27_docindex.py +777 -774
  152. package/md_cg/test_p28_refcheck.py +740 -13
  153. package/md_cg/test_p29_session_ingest_export.py +2 -2
  154. package/md_cg/test_p2_mcp.py +12 -1
  155. package/md_cg/test_p2_six_elements.py +463 -0
  156. package/md_cg/test_p30_maintain.py +33 -2
  157. package/md_cg/test_p31_insight.py +1 -0
  158. package/md_cg/test_p3_legacy_closure.py +433 -0
  159. package/md_cg/test_p4_freshness.py +680 -0
  160. package/md_cg/test_p8_subgraph_chain.py +14 -1
  161. package/md_cg/test_p9c_dedup_hints.py +276 -0
  162. package/md_cg/test_policy_required_ccg.py +426 -0
  163. package/md_cg/test_protocol.py +22 -0
  164. package/md_cg/test_rank_parity_score_mode.py +522 -0
  165. package/md_cg/test_read_face_input_gates.py +363 -0
  166. package/md_cg/test_read_face_semantics.py +489 -0
  167. package/md_cg/test_recall_face_guards.py +812 -0
  168. package/md_cg/test_rejected_credential_forms.py +188 -0
  169. package/md_cg/test_rejected_redact.py +146 -0
  170. package/md_cg/test_retr_s1b.py +2 -2
  171. package/md_cg/test_retr_s5.py +20 -7
  172. package/md_cg/test_review_conformance.py +21 -3
  173. package/md_cg/test_security_audit_v21.py +2 -2
  174. package/md_cg/test_semantic_canonical.py +5 -3
  175. package/md_cg/test_server_version.py +67 -0
  176. package/md_cg/test_sleep.py +611 -0
  177. package/md_cg/test_sleep_p1.py +784 -0
  178. package/md_cg/test_srcindex.py +171 -0
  179. package/md_cg/test_tenant_env_override_warn.py +63 -50
  180. package/md_cg/test_time_core_lint.py +968 -0
  181. package/md_cg/test_token_lowercase_form.py +315 -0
  182. package/md_cg/test_unify_default_off.py +701 -0
  183. package/md_cg/test_unify_scope.py +182 -0
  184. package/md_cg/test_v21r1_package_version.py +310 -0
  185. package/md_cg/test_writepipe.py +10 -4
  186. package/md_cg/tokens.py +225 -95
  187. package/md_cg/tool_face.py +4 -4
  188. package/md_cg/trust.py +49 -5
  189. package/md_cg/units.py +204 -6
  190. package/md_cg/weights.py +4 -4
  191. package/md_cg/whitebox_kb/aeis_core/time_core.py +8 -0
  192. package/md_cg/whitebox_kb/wisdom/knowledge_points.py +1 -1
  193. package/md_cg/writelimit.py +42 -8
  194. package/md_cg/writepipe.py +651 -554
  195. package/package.json +4 -2
  196. package/skills/plugin.json +1 -1
  197. package/src/bridge.ts +25 -2
  198. package/src/hooks.ts +229 -16
  199. package/src/init.ts +15 -1
  200. package/src/lib/prompt_safety.ts +88 -1
  201. package/utf8_boot.py +237 -0
  202. package/zcode/AGENTS.md +10 -10
@@ -33,7 +33,7 @@ import shutil
33
33
  import sys
34
34
  import tempfile
35
35
 
36
- from . import ccgc, consolidate, crosscheck, vision_evidence
36
+ from . import ccgc, consolidate, crosscheck, tokens, vision_evidence
37
37
  from .mdcos import MdCGOS
38
38
 
39
39
  PASS = FAIL = 0
@@ -181,9 +181,21 @@ def g2_link(tmp):
181
181
  r = ccgc.compile_dialog(DIALOG, "gd_link", "agent:a1",
182
182
  slots=SLOTS, marks=MARKS, cg=a)
183
183
  ok(r.success, "G2a 编译成功")
184
+ # N176 契约:落库准入须令牌验证方——哑令牌临时文件 + 临时 MDCG_TOKEN_FILE
185
+ # (attest→verify_token 走环境变量定位令牌库,绝不触真实令牌库)
186
+ tok_env = os.path.join(tmp, "g2_tokens.json")
187
+ _old_tf = os.environ.get(tokens.TOKEN_FILE_ENV)
188
+ os.environ[tokens.TOKEN_FILE_ENV] = tok_env
189
+ tk = tokens.issue("designer", actor="designer", path=tok_env)["token"]
184
190
  a_ok = ccgc.attest("gd_link", ccgc.ACCEPT, "designer", "agent:a1",
185
- evidence="守卫:候选与对话原文一致", cg=a)
186
- ok(a_ok.ok, "G2b 编外验证方签章")
191
+ evidence="守卫:候选与对话原文一致", cg=a,
192
+ verifier_token=tk)
193
+ if _old_tf is None:
194
+ os.environ.pop(tokens.TOKEN_FILE_ENV, None)
195
+ else:
196
+ os.environ[tokens.TOKEN_FILE_ENV] = _old_tf
197
+ ok(a_ok.ok and a_ok.verifier_identity == "token", "G2b 编外验证方签章"
198
+ "(N176:令牌凭据,self-reported 不构成落库准入)")
187
199
  l = ccgc.link(r, a_ok, cg=a, apply=True)
188
200
  ok(l.ok and l.written == 6, "G2c link 写入六行")
189
201
  fm, _c = _disk(root, "gd_link")
@@ -308,7 +320,8 @@ def _g89_root(tmp):
308
320
  a = MdCGOS(root)
309
321
  a.add("gd_pro",
310
322
  "# 功能名:待提升节点\n# 生效条件:问提升\n# 子功能:说明提升\n"
311
- "# 执行:按热度\n# 不适用条件:问无关\n",
323
+ "# 执行:按热度\n# 验证方式:编译器/静态检查通过\n"
324
+ "# 不适用条件:问无关\n",
312
325
  layer="contextual", merge_count=2)
313
326
  a.flush()
314
327
  return root
@@ -0,0 +1,233 @@
1
+ # -*- coding: utf-8 -*-
2
+ """md_cg · H2:会话视图的读侧归一(「写进去能查出来」)
3
+
4
+ 病灶(端到端实测):写侧落盘值 = `_normalize_session(请求声明值)`
5
+ (`MdCGSecure._attribution` 取 `cg.session`,而 `cg.session` 由 `call_tool`
6
+ → `_declared_session` 归一);读侧 `stg.timeline` 却拿**未归一的原值**做
7
+ 等值比较 ⇒ 同一条记忆「写进去查不出」——DSH 形态的会话 id 在会话根下不
8
+ 存在时写侧落 `anonymous`,读侧按 `session-…` 精确匹配得 `count=0`。
9
+
10
+ 修复(一处归一,改在视图层):`stg._view_session` 只对「具体会话值」这一态
11
+ 消费 `mcp_server._normalize_session`(真源唯一,**不重写实现**),三态语义
12
+ (None / "" / "*" = 跨会话)逐位不变。
13
+
14
+ 覆盖
15
+ ----
16
+ A 端到端:MCP 写一条声明了 DSH 形态 session 的记忆 → 落盘值是归一值,
17
+ 再用**原始声明值**读回来必须命中(H2 的核心断言)
18
+ B 归一真源唯一:读侧消费的确实是 `mcp_server._normalize_session`
19
+ (哨兵替换法——防后续有人在 stg 里重写一把尺,那必然造出第三种不等值)
20
+ C 三态视图不回归:None / "" / "*" 仍跨会话;具体非 DSH 值仍精确匹配
21
+ D fail-soft:会话根不可读(OSError)时读写两侧都保原值,仍能读回
22
+ E 不越界:cg 侧读路径(search)不吃请求 session(issue #35 定稿:身份不可自报)
23
+
24
+ 运行:python -m md_cg.test_h2_session_view_norm
25
+ """
26
+ from __future__ import annotations
27
+
28
+ import os
29
+ import shutil
30
+ import sys
31
+ import tempfile
32
+
33
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
34
+
35
+ # Windows cmd 默认 GBK:中文打印即 UnicodeEncodeError,且崩在断言之后、报告之前。
36
+ if hasattr(sys.stdout, "reconfigure"):
37
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
38
+
39
+ from md_cg.mdcos import MdCGSecure
40
+ from md_cg.security import DEFAULT_SENSITIVITY, Principal
41
+ from md_cg import mcp_server as ms
42
+
43
+ PASS = 0
44
+ FAILS = []
45
+
46
+ # 合成会话 id(uuid4 形态的 DSH 会话;非真实凭据,仅形态样本)
47
+ DSH_ID = "session-11111111-2222-3333-4444-555555555555"
48
+ ENV_KEY = "MDCG_DSH_SESSIONS_ROOT"
49
+
50
+
51
+ def check(name, cond, detail=""):
52
+ global PASS
53
+ if cond:
54
+ PASS += 1
55
+ print(f" [PASS] {name}" + (f" · {detail}" if detail else ""))
56
+ else:
57
+ FAILS.append(name)
58
+ print(f" [FAIL] {name} · {str(detail)[:240]}")
59
+
60
+
61
+ def _p(actor, session):
62
+ return Principal(actor=actor, clearance=DEFAULT_SENSITIVITY,
63
+ can_write=True, can_admin=True, role="designer",
64
+ session=session, harness="test-harness")
65
+
66
+
67
+ def _remember(cg, text, session):
68
+ """经 MCP 写入口落一条(gated 路径,与插件自动记忆同一条链)。"""
69
+ return ms.call_tool(cg, "mdcg_remember", {
70
+ "content": text, "layer": "contextual", "session": session,
71
+ "gated": True, "importance": 0.6})
72
+
73
+
74
+ def _timeline(cg, session):
75
+ return ms.call_tool(cg, "stg", {"op": "timeline", "limit": 20, "session": session})
76
+
77
+
78
+ def test_a(root, sessions_root):
79
+ """A 写进去能查出来(核心断言)。"""
80
+ print("\n[A] 端到端:DSH 形态声明 → 落盘归一值 → 原值读回命中")
81
+ old = os.environ.get(ENV_KEY)
82
+ os.environ[ENV_KEY] = sessions_root # 根可读、但无该会话
83
+ try:
84
+ # 前置条件断言:本用例的环境确实触发归一,否则下面的断言是空的
85
+ check("A0 前置:该环境下 DSH 形态归一为 anonymous",
86
+ ms._normalize_session(DSH_ID) == "anonymous",
87
+ ms._normalize_session(DSH_ID))
88
+ cg = MdCGSecure(root, principal=_p("alice", "sess_A"))
89
+ res = _remember(cg, "阿尔法 会话视图归一 端到端写入样本", DSH_ID)
90
+ nid = res.get("written") or res.get("node_id")
91
+ check("A1 写入落盘(ACCEPT 且有节点 id)",
92
+ bool(nid) and res.get("verdict") == "ACCEPT",
93
+ (res.get("verdict"), nid))
94
+ fm = (cg.get(nid) or {}).get("frontmatter") or {}
95
+ check("A2 落盘 session = 归一声明值(写侧口径)",
96
+ fm.get("session") == "anonymous", fm.get("session"))
97
+ tl = _timeline(cg, DSH_ID)
98
+ check("A3 用**原始声明值**读回命中(H2 核心:写进去查得出)",
99
+ tl.get("count") == 1 and [i["id"] for i in tl.get("items", [])] == [nid],
100
+ (tl.get("count"), [i["id"] for i in tl.get("items", [])[:3]]))
101
+ check("A4 返回体 session = 归一后的生效值(诚实回带)",
102
+ tl.get("session") == "anonymous", tl.get("session"))
103
+ finally:
104
+ if old is None:
105
+ os.environ.pop(ENV_KEY, None)
106
+ else:
107
+ os.environ[ENV_KEY] = old
108
+
109
+
110
+ def test_b(root, sessions_root):
111
+ """B 归一真源唯一(哨兵法)。"""
112
+ print("\n[B] 读侧消费 mcp_server 真源(不重写一把尺)")
113
+ old = os.environ.get(ENV_KEY)
114
+ os.environ[ENV_KEY] = sessions_root
115
+ orig = ms._normalize_session
116
+ try:
117
+ cg = MdCGSecure(root, principal=_p("alice", "sess_A"))
118
+ ms._normalize_session = lambda raw: "SENTINEL_" + str(raw)
119
+ out = _timeline(cg, "probe")
120
+ check("B1 具体值经 mcp_server._normalize_session(哨兵可见)",
121
+ out.get("session") == "SENTINEL_probe", out.get("session"))
122
+ star = _timeline(cg, "*")
123
+ check("B2 \"*\" 不经归一(跨会话开关是不变量)",
124
+ star.get("session") is None, star.get("session"))
125
+ blank = _timeline(cg, "")
126
+ check("B3 空串不经归一(缺省语义不变量)",
127
+ blank.get("session") is None, blank.get("session"))
128
+ finally:
129
+ ms._normalize_session = orig
130
+ if old is None:
131
+ os.environ.pop(ENV_KEY, None)
132
+ else:
133
+ os.environ[ENV_KEY] = old
134
+
135
+
136
+ def test_c(root, sessions_root):
137
+ """C 三态视图不回归 + 具体值精确匹配。"""
138
+ print("\n[C] 视图三态与精确匹配不回归")
139
+ old = os.environ.get(ENV_KEY)
140
+ os.environ[ENV_KEY] = sessions_root
141
+ try:
142
+ cg = MdCGSecure(root, principal=_p("alice", "sess_A"))
143
+ _remember(cg, "贝塔 属于 会话 sess_A 的知识点", "sess_A")
144
+ a = _timeline(cg, "sess_A")
145
+ check("C1 具体非 DSH 值精确匹配(原样采用)",
146
+ a.get("count") == 1 and a.get("session") == "sess_A",
147
+ (a.get("count"), a.get("session")))
148
+ z = _timeline(cg, "sess_Z")
149
+ check("C2 未知会话为空(不因缺省而放行)",
150
+ z.get("count") == 0, z.get("count"))
151
+ for label, val in (("缺省", None), ("空串", ""), ("星号", "*")):
152
+ out = _timeline(cg, val)
153
+ check(f"C3 {label}仍为跨会话视图(返回体 session=None 且非空)",
154
+ out.get("session") is None and out.get("count") >= 1,
155
+ (out.get("session"), out.get("count")))
156
+ finally:
157
+ if old is None:
158
+ os.environ.pop(ENV_KEY, None)
159
+ else:
160
+ os.environ[ENV_KEY] = old
161
+
162
+
163
+ def test_d(root, tmp):
164
+ """D fail-soft:根不可读 → 两侧都保原值。"""
165
+ print("\n[D] 会话根不可读(OSError)时 fail-soft 读写一致")
166
+ old = os.environ.get(ENV_KEY)
167
+ os.environ[ENV_KEY] = os.path.join(tmp, "no_such_root_dir")
168
+ try:
169
+ check("D1 根不可读 → 归一保原值",
170
+ ms._normalize_session(DSH_ID) == DSH_ID,
171
+ ms._normalize_session(DSH_ID))
172
+ cg = MdCGSecure(root, principal=_p("alice", "sess_A"))
173
+ res = _remember(cg, "伽马 根不可读 场景写入样本", DSH_ID)
174
+ nid = res.get("written") or res.get("node_id")
175
+ fm = (cg.get(nid) or {}).get("frontmatter") or {}
176
+ check("D2 落盘保原值(不因环境差异丢会话标记)",
177
+ fm.get("session") == DSH_ID, fm.get("session"))
178
+ tl = _timeline(cg, DSH_ID)
179
+ check("D3 同环境下读回命中(读写两侧同尺)",
180
+ tl.get("count") == 1, tl.get("count"))
181
+ finally:
182
+ if old is None:
183
+ os.environ.pop(ENV_KEY, None)
184
+ else:
185
+ os.environ[ENV_KEY] = old
186
+
187
+
188
+ def test_e(root, sessions_root):
189
+ """E 不越界:cg 侧读路径不吃请求 session(issue #35 定稿)。"""
190
+ print("\n[E] 不越界:search 的 session 不构成授权/过滤豁免")
191
+ old = os.environ.get(ENV_KEY)
192
+ os.environ[ENV_KEY] = sessions_root
193
+ try:
194
+ cg = MdCGSecure(root, principal=_p("alice", "sess_A"))
195
+ _remember(cg, "德尔塔 共享档检索样本", "sess_A")
196
+ res_default, _ = cg.search("德尔塔", judge=False, record=False)
197
+ res_dsh, _ = cg.search("德尔塔", session=DSH_ID, judge=False, record=False)
198
+ ids_default = {r[0].get("id") if isinstance(r, tuple) else r.get("id")
199
+ for r in res_default}
200
+ ids_dsh = {r[0].get("id") if isinstance(r, tuple) else r.get("id")
201
+ for r in res_dsh}
202
+ check("E1 传 DSH 形态 session 与缺省结果一致(cg 侧不经 _view_session)",
203
+ ids_default == ids_dsh and bool(ids_default),
204
+ (sorted(ids_default)[:3], sorted(ids_dsh)[:3]))
205
+ finally:
206
+ if old is None:
207
+ os.environ.pop(ENV_KEY, None)
208
+ else:
209
+ os.environ[ENV_KEY] = old
210
+
211
+
212
+ def main():
213
+ tmp = tempfile.mkdtemp(prefix="mdcg_h2_")
214
+ sessions_root = os.path.join(tmp, "sessions")
215
+ os.makedirs(sessions_root, exist_ok=True)
216
+ try:
217
+ test_a(os.path.join(tmp, "root_a"), sessions_root)
218
+ test_b(os.path.join(tmp, "root_b"), sessions_root)
219
+ test_c(os.path.join(tmp, "root_c"), sessions_root)
220
+ test_d(os.path.join(tmp, "root_d"), tmp)
221
+ test_e(os.path.join(tmp, "root_e"), sessions_root)
222
+ finally:
223
+ shutil.rmtree(tmp, ignore_errors=True)
224
+
225
+ print("\n通过 %d / 失败 %d" % (PASS, len(FAILS)))
226
+ if FAILS:
227
+ print("失败项:" + ", ".join(FAILS))
228
+ return 1
229
+ return 0
230
+
231
+
232
+ if __name__ == "__main__":
233
+ sys.exit(main())