@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,310 @@
1
+ # -*- coding: utf-8 -*-
2
+ """md_cg · 包版本读取 fail-closed 守卫(v21-R1:非对象 package.json 让服务起不来)
3
+
4
+ 缺陷(修复前):`md_cg/mcp_server.py:47-53` 与 `hive/hive_mcp/mcp_server.py:48-54`
5
+ 的 `_package_version` 只捉 `(OSError, ValueError)`,随后**无条件** `.get("version")`:
6
+ · `package.json` 是**合法 JSON 但顶层非对象**(`[1,2]` / `"0.1.0"` / `123` /
7
+ `null` / `true`)→ `.get` 抛 AttributeError,**逃出 except**;
8
+ · 而 `SERVER_VERSION = _package_version()`(md_cg:57 / hive:58)是**模块级
9
+ 立即求值**——异常发生在 import 期,`python -m md_cg.mcp_server` / `-m
10
+ hive.hive_mcp.mcp_server` 连 initialize 都发不出就退出(服务起不来,
11
+ 与「版本号退化」完全不是一个量级);
12
+ · `{"version": 123}` 不抛但把 **int** 当版本号塞进 initialize 的
13
+ serverInfo.version(协议要求字符串),调用方拿到错类型。
14
+
15
+ 修复:`_package_version` 只认「读到 dict 且 "version" 为**非空字符串**」,其余
16
+ (不可读/JSON 非法/顶层非对象/version 非字符串/空串)一律回落 "0.1.0";except
17
+ 补 TypeError/AttributeError 兜底。同族既有守卫 `md_cg/test_server_version.py`
18
+ 只覆盖「好文件 → 版本联动」,本守卫补**坏文件**面。
19
+
20
+ 守卫(四组,全部临时包根 + 哑主密钥/哑令牌,绝不写真实 package.json、绝不触
21
+ 真实 ~/.mdcg):
22
+ [1] 变体矩阵(16 形态 × 2 模块,真实 import + importlib.reload 重跑模块级
23
+ `SERVER_VERSION = _package_version()`):修前 `[1,2]` 等非对象形态报
24
+ AttributeError、`{"version": 123}` 得 int 123;修后一律回落 "0.1.0",
25
+ 合法形态("9.9.9"/"0.6.0")原样;
26
+ [2] hive 握手端到端(真实 stdio 子进程,临时包根内 `package.json=[1,2]`):
27
+ 修前进程 import 期即死、无任何响应;修后服务起得来且 initialize 自报
28
+ "0.1.0";换合法 version 时自报该值(证明该腿真有判别力,不是常量);
29
+ [3] md_cg 握手端到端(同上,另加临时 MDCG_ROOT + 哑令牌/哑主密钥);
30
+ [4] 不回归:真实仓库的 md_cg/hive 两模块 SERVER_VERSION 仍等于真实
31
+ package.json 的 version(既有 test_server_version.py 口径)。
32
+
33
+ 运行:python -m md_cg.test_v21r1_package_version
34
+ """
35
+ from __future__ import annotations
36
+
37
+ import gc
38
+ import glob
39
+ import json
40
+ import os
41
+ import shutil
42
+ import subprocess
43
+ import sys
44
+ import tempfile
45
+ import time
46
+
47
+ from . import crypto, tokens
48
+
49
+ PASS = FAIL = 0
50
+ FAILS = []
51
+
52
+ REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
53
+ DUMMY_KEK = "ab" * 32 # 哑主密钥:子进程绝不触真实密钥库
54
+
55
+
56
+ def _clean(root):
57
+ """尽力清理临时包根:Windows 下句柄未释放会让 rmtree 静默失败,故重试并告警。"""
58
+ for _ in range(4):
59
+ shutil.rmtree(root, ignore_errors=True)
60
+ if not os.path.exists(root):
61
+ return
62
+ gc.collect()
63
+ time.sleep(0.2)
64
+ print(" [warn] 临时目录未能清理(句柄占用?):%s" % os.path.basename(root))
65
+
66
+ # (package.json 正文(None=删文件), 期望版本)
67
+ VARIANTS = [
68
+ ("[1, 2]", "0.1.0"), # 顶层数组(AttributeError 逃逸)
69
+ ('"0.9.9"', "0.1.0"), # 顶层裸字符串
70
+ ("123", "0.1.0"), # 顶层数字
71
+ ("null", "0.1.0"), # 顶层 null
72
+ ("true", "0.1.0"), # 顶层 bool
73
+ ('{"version": 123}', "0.1.0"), # int 版本号曾原样进握手
74
+ ('{"version": ["9.9.9"]}', "0.1.0"),
75
+ ('{"version": {"a": 1}}', "0.1.0"),
76
+ ('{"version": null}', "0.1.0"),
77
+ ('{"version": ""}', "0.1.0"),
78
+ ('{"version": " "}', "0.1.0"),
79
+ ('{"name": "x"}', "0.1.0"), # 无 version 键
80
+ ("{", "0.1.0"), # JSON 非法(ValueError 老路)
81
+ (None, "0.1.0"), # 文件缺失
82
+ ('{"version": "9.9.9"}', "9.9.9"), # 合法
83
+ ('{"version": "0.6.0", "extra": [1]}', "0.6.0"), # 合法(多键)
84
+ ]
85
+
86
+ DRIVER = '''# -*- coding: utf-8 -*-
87
+ """测试驱动:逐变体写 package.json 后 reload 两个 server 模块并回报结果。
88
+
89
+ 只做两件事:真实 import(触发模块级 `SERVER_VERSION = _package_version()`)
90
+ 与 importlib.reload(重跑同一句)——即缺陷的**真实消费点**,不改被测代码。
91
+ """
92
+ import importlib
93
+ import json
94
+ import os
95
+ import sys
96
+
97
+ HERE = os.path.dirname(os.path.abspath(__file__))
98
+ MODS = ("md_cg.mcp_server", "hive.hive_mcp.mcp_server")
99
+ VARIANTS = json.load(open(os.path.join(HERE, "_variants.json"), encoding="utf-8"))
100
+
101
+
102
+ def write_pkg(body):
103
+ p = os.path.join(HERE, "package.json")
104
+ if body is None:
105
+ if os.path.exists(p):
106
+ os.remove(p)
107
+ return
108
+ with open(p, "w", encoding="utf-8") as f:
109
+ f.write(body)
110
+
111
+
112
+ def probe():
113
+ out = {}
114
+ for name in MODS:
115
+ try:
116
+ m = importlib.import_module(name) # 首次 import
117
+ except Exception as e: # noqa: BLE001
118
+ out[name] = ["IMPORT_ERR", type(e).__name__]
119
+ continue
120
+ try:
121
+ m = importlib.reload(m) # 重跑模块级赋值
122
+ v = m.SERVER_VERSION
123
+ out[name] = ["OK", repr(v), type(v).__name__]
124
+ except Exception as e: # noqa: BLE001
125
+ out[name] = ["RELOAD_ERR", type(e).__name__]
126
+ return out
127
+
128
+
129
+ results = []
130
+ write_pkg('{"version": "0.0.0"}') # 先保证两模块能 import
131
+ for i, body in enumerate(VARIANTS):
132
+ write_pkg(body)
133
+ results.append({"i": i, "r": probe()})
134
+ print(json.dumps(results))
135
+ '''
136
+
137
+
138
+ def check(name, cond, detail=""):
139
+ global PASS, FAIL
140
+ if cond:
141
+ PASS += 1
142
+ print(f" [PASS] {name}" + (f" · {detail}" if detail else ""))
143
+ else:
144
+ FAIL += 1
145
+ FAILS.append(f"{name} · {detail}" if detail else name)
146
+ print(f" [FAIL] {name}" + (f" · {detail}" if detail else ""))
147
+
148
+
149
+ def _mk_pkg_root(tmp, name: str) -> str:
150
+ """临时包根:md_cg/ 与 hive/hive_mcp/ 的真实源码副本 + 根 package.json 占位。
151
+
152
+ 副本是**当前源码**(改动即生效),根下 package.json 决定 `_package_version`
153
+ 读到的内容——真实 __file__ 推导,不 mock 任何函数。
154
+ """
155
+ root = os.path.join(tmp, name)
156
+ os.makedirs(root, exist_ok=True)
157
+ shutil.copytree(os.path.join(REPO, "md_cg"), os.path.join(root, "md_cg"),
158
+ ignore=shutil.ignore_patterns("__pycache__"))
159
+ os.makedirs(os.path.join(root, "hive", "hive_mcp"), exist_ok=True)
160
+ shutil.copy(os.path.join(REPO, "hive", "__init__.py"),
161
+ os.path.join(root, "hive", "__init__.py"))
162
+ for fn in glob.glob(os.path.join(REPO, "hive", "hive_mcp", "*.py")):
163
+ shutil.copy(fn, os.path.join(root, "hive", "hive_mcp", os.path.basename(fn)))
164
+ with open(os.path.join(root, "package.json"), "w", encoding="utf-8") as f:
165
+ f.write('{"version": "0.0.0"}')
166
+ return root
167
+
168
+
169
+ def _dry_run(root, variants) -> list:
170
+ """跑驱动(真实 import/reload),返回每个变体的逐模块结果。"""
171
+ with open(os.path.join(root, "_variants.json"), "w", encoding="utf-8") as f:
172
+ json.dump([b for b, _e in variants], f, ensure_ascii=False)
173
+ with open(os.path.join(root, "_drv.py"), "w", encoding="utf-8") as f:
174
+ f.write(DRIVER)
175
+ env = dict(os.environ)
176
+ env["PYTHONPATH"] = REPO # 同级仓库根模块(aeis_core 等)
177
+ env["PYTHONUTF8"] = "1"
178
+ p = subprocess.run([sys.executable, "-X", "utf8", "_drv.py"], cwd=root,
179
+ env=env, capture_output=True, text=True,
180
+ encoding="utf-8", timeout=300)
181
+ if p.returncode != 0:
182
+ raise RuntimeError("驱动失败 rc=%s stderr=%s" % (p.returncode,
183
+ (p.stderr or "")[-400:]))
184
+ return json.loads(p.stdout.strip().splitlines()[-1])
185
+
186
+
187
+ def _handshake(root, module: str, env: dict):
188
+ """真实 stdio 子进程发 initialize,返回 (版本, 原始响应, stderr)。"""
189
+ p = subprocess.Popen([sys.executable, "-X", "utf8", "-m", module],
190
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
191
+ stderr=subprocess.PIPE, text=True, encoding="utf-8",
192
+ env=env, cwd=root)
193
+ try:
194
+ p.stdin.write(json.dumps({"jsonrpc": "2.0", "id": 1, "method": "initialize",
195
+ "params": {"protocolVersion": "2024-11-05",
196
+ "capabilities": {},
197
+ "clientInfo": {"name": "v21r1",
198
+ "version": "1"}}}) + "\n")
199
+ p.stdin.flush()
200
+ line = p.stdout.readline() # 进程死 → EOF,不阻塞
201
+ if not line:
202
+ return None, None, (p.stderr.read() or "")[-300:]
203
+ resp = json.loads(line)
204
+ ver = ((resp.get("result") or {}).get("serverInfo") or {}).get("version")
205
+ return ver, resp, ""
206
+ finally:
207
+ try:
208
+ p.stdin.write(json.dumps({"jsonrpc": "2.0", "id": 2,
209
+ "method": "shutdown"}) + "\n")
210
+ p.stdin.flush()
211
+ p.wait(timeout=10)
212
+ except Exception: # noqa: BLE001
213
+ p.kill()
214
+
215
+
216
+ def main():
217
+ print("=" * 68)
218
+ print("包版本读取 fail-closed(v21-R1)")
219
+ print("=" * 68)
220
+ tmp = tempfile.mkdtemp(prefix="v21r1_pkg_")
221
+ try:
222
+ root = _mk_pkg_root(tmp, "pkg")
223
+ mods = ("md_cg.mcp_server", "hive.hive_mcp.mcp_server")
224
+
225
+ # ---------- [1] 变体矩阵(真实 import + reload) ----------
226
+ print("\n[1] 变体矩阵(16 形态 × 2 模块,真实 import/reload)")
227
+ res = _dry_run(root, VARIANTS)
228
+ for rec, (body, want) in zip(res, VARIANTS):
229
+ for m in mods:
230
+ got = rec["r"].get(m) or ["<缺失>"]
231
+ ok = got[0] == "OK" and got[1] == repr(want) and got[2] == "str"
232
+ check(f"[1] {m.split('.')[0]}:{body!r} → {want!r}(必须 str)", ok,
233
+ f"got {got!r}")
234
+
235
+ # ---------- [2] hive 握手端到端 ----------
236
+ print("\n[2] hive 握手端到端(临时包根 package.json=[1, 2])")
237
+ bad_root = _mk_pkg_root(tmp, "pkg_bad")
238
+ with open(os.path.join(bad_root, "package.json"), "w", encoding="utf-8") as f:
239
+ f.write("[1, 2]")
240
+ env = dict(os.environ)
241
+ env["PYTHONPATH"] = REPO
242
+ env["PYTHONUTF8"] = "1"
243
+ ver, resp, err = _handshake(bad_root, "hive.hive_mcp.mcp_server", env)
244
+ check("[2] 绿:服务起得来且握手自报回落版本(修前 import 期 AttributeError,无响应)",
245
+ isinstance(ver, str) and ver == "0.1.0",
246
+ f"ver={ver!r} stderr={err!r}")
247
+ check("[2] 绿:serverInfo.version 为字符串(协议要求)",
248
+ isinstance(ver, str), f"{type(ver).__name__}")
249
+ with open(os.path.join(bad_root, "package.json"), "w", encoding="utf-8") as f:
250
+ f.write('{"version": "9.9.9"}')
251
+ ver2, _r2, err2 = _handshake(bad_root, "hive.hive_mcp.mcp_server", env)
252
+ check("[2] 判别力对照:合法 version 时握手自报 9.9.9", ver2 == "9.9.9",
253
+ f"ver={ver2!r} stderr={err2!r}")
254
+
255
+ # ---------- [3] md_cg 握手端到端 ----------
256
+ print("\n[3] md_cg 握手端到端(临时包根 + 临时 MDCG_ROOT + 哑令牌/哑密钥)")
257
+ cg_root = os.path.join(tmp, "cg_lib")
258
+ tf = os.path.join(tmp, "_tokens.json")
259
+ tok = tokens.issue("designer", actor="v21r1", path=tf)
260
+ aux = os.path.join(tmp, "auxdir") # 注:不能叫 aux(Win32 保留设备名)
261
+ os.makedirs(aux, exist_ok=True)
262
+ env2 = dict(os.environ)
263
+ env2.update({"PYTHONPATH": REPO, "PYTHONUTF8": "1",
264
+ "MDCG_ROOT": cg_root, "MDCG_TOKEN_FILE": tf,
265
+ "MDCG_TOKEN": tok["token"], "MDCG_MASTER_KEY": DUMMY_KEK,
266
+ "MDCG_AUX_ROOT": aux, "MDCG_SUSTAIN": "0",
267
+ "MDCG_SUSTAIN_ENABLED": "0"})
268
+ for k in ("MDCG_LEGACY_ENV_AUTH", "MDCG_CAN_ADMIN", "MDCG_CAN_WRITE",
269
+ "MDCG_CLEARANCE", "MDCG_ACTOR", "MDCG_LEGACY_ENV_ADMIN"):
270
+ env2.pop(k, None)
271
+ check("[3] 隔离声明:临时库 + 哑令牌 + 哑主密钥",
272
+ os.path.abspath(cg_root).startswith(os.path.abspath(tmp))
273
+ and env2["MDCG_MASTER_KEY"] == "ab" * 32
274
+ and env2["MDCG_TOKEN"] == tok["token"])
275
+ root_bad = _mk_pkg_root(tmp, "pkg_bad2")
276
+ with open(os.path.join(root_bad, "package.json"), "w", encoding="utf-8") as f:
277
+ f.write("[1, 2]")
278
+ ver3, _r3, err3 = _handshake(root_bad, "md_cg.mcp_server", env2)
279
+ check("[3] 绿:md_cg 服务起得来且握手自报回落版本", ver3 == "0.1.0",
280
+ f"ver={ver3!r} stderr={err3!r}")
281
+ check("[3] 绿:握手可用(result 结构完整,证明不是半启动)",
282
+ isinstance(_r3, dict) and (_r3.get("result") or {}).get("serverInfo"),
283
+ str(_r3)[:120])
284
+
285
+ # ---------- [4] 不回归:真实包根的版本联动 ----------
286
+ print("\n[4] 不回归:真实 package.json 版本仍被两模块读到")
287
+ from . import mcp_server as mdcg_server
288
+ from hive.hive_mcp import mcp_server as hive_server
289
+ with open(os.path.join(REPO, "package.json"), encoding="utf-8") as f:
290
+ want = json.load(f)["version"]
291
+ check("[4] md_cg SERVER_VERSION == 真实 package.json version",
292
+ mdcg_server.SERVER_VERSION == want,
293
+ f"got {mdcg_server.SERVER_VERSION!r} want {want!r}")
294
+ check("[4] hive SERVER_VERSION == 真实 package.json version",
295
+ hive_server.SERVER_VERSION == want,
296
+ f"got {hive_server.SERVER_VERSION!r} want {want!r}")
297
+ check("[4] 两者均为 str", all(isinstance(x, str) for x in
298
+ (mdcg_server.SERVER_VERSION, hive_server.SERVER_VERSION)))
299
+ finally:
300
+ _clean(tmp)
301
+
302
+ print("\n" + "=" * 68)
303
+ print(f"PASS={PASS} FAIL={FAIL}")
304
+ if FAILS:
305
+ print("失败项:" + ";".join(FAILS))
306
+ return 0 if FAIL == 0 else 1
307
+
308
+
309
+ if __name__ == "__main__":
310
+ sys.exit(main())
@@ -45,8 +45,9 @@ def _mk_cg(tmp, role="designer"):
45
45
  return MdCGSecure(root, principal=p)
46
46
 
47
47
 
48
- def _policy(tmp, required=("PASSED",), forbidden=("FORBIDDEN_WORD",)):
49
- path = os.path.join(tmp, "policy.json")
48
+ def _policy(tmp, required=("PASSED",), forbidden=("FORBIDDEN_WORD",),
49
+ name="policy.json"):
50
+ path = os.path.join(tmp, name)
50
51
  with open(path, "w", encoding="utf-8") as f:
51
52
  json.dump({"forbidden": list(forbidden),
52
53
  "required": list(required)}, f)
@@ -93,8 +94,13 @@ def _run(tmp):
93
94
  _check("REJECT负记忆", out.get("moved_to") == "rejected"
94
95
  and out.get("committed") is False, repr(out))
95
96
 
96
- # DEFER(无规则文件 → 无法判定 → 入审核队列)+ 幂等去重联动
97
- os.environ.pop("MDCG_POLICY_FILE", None)
97
+ # DEFER(空规则库 → 无法判定 → 入审核队列)+ 幂等去重联动
98
+ # 迁移(issue #43,2026-09-29):修前「未设 env」= 空规则 → DEFER;修后未设
99
+ # 会回落到包内默认 data/policy.json,本用例内容无六要素 → 变 REJECT。此处
100
+ # 考的是 **DEFER 出口 + 幂等去重联动**(与策略来源无关),故显式给一份空
101
+ # 规则库复现原口径——断言一字不改。
102
+ os.environ["MDCG_POLICY_FILE"] = _policy(tmp, required=(), forbidden=(),
103
+ name="policy_empty.json")
98
104
  a = {"content_kind": "text", "content": "无规则可判的内容",
99
105
  "layer": "knowledge"}
100
106
  out1 = pipe.execute(cg, dict(a))