@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.
- package/README.md +618 -572
- package/codebuddy/CODEBUDDY.md +10 -10
- package/data/policy.json +27 -0
- package/docs/README.md +1 -0
- package/docs/discipline/harnesses.yaml +31 -0
- package/docs/discipline/templates/full.md.tmpl +1 -1
- package/docs/discipline/templates/rules.mdc.tmpl +1 -1
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +100 -0
- 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
- package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +57 -9
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/lib/bridge.js +24 -2
- package/lib/hooks.d.ts +31 -2
- package/lib/hooks.js +219 -16
- package/lib/init.js +16 -1
- package/lib/lib/prompt_safety.d.ts +52 -1
- package/lib/lib/prompt_safety.js +76 -1
- package/md_cg/audit.py +652 -379
- package/md_cg/bench6_arms.py +1 -1
- package/md_cg/bench_e2e_locomo_qa.py +11 -4
- package/md_cg/bench_p0.py +1 -1
- package/md_cg/branches.py +2 -2
- package/md_cg/ccgc.py +1071 -1006
- package/md_cg/chain.py +48 -1
- package/md_cg/consistency.py +97 -9
- package/md_cg/consolidate.py +34 -9
- package/md_cg/crypto.py +59 -26
- package/md_cg/docindex.py +16 -1
- package/md_cg/evolution.py +17 -1
- package/md_cg/export.py +1 -1
- package/md_cg/forgetting.py +309 -13
- package/md_cg/freshness.py +511 -0
- package/md_cg/fsutil.py +454 -8
- package/md_cg/generation.py +409 -0
- package/md_cg/hotcache.py +4 -1
- package/md_cg/identity.py +3 -3
- package/md_cg/insight.py +24 -3
- package/md_cg/linkref.py +1 -1
- package/md_cg/logref.py +327 -0
- package/md_cg/mcp_server.py +4261 -3818
- package/md_cg/mdcg.py +4695 -3547
- package/md_cg/mdcos.py +4672 -4159
- package/md_cg/mreview/pipeline.py +15 -1
- package/md_cg/nodefile.py +713 -576
- package/md_cg/protect.py +158 -11
- package/md_cg/protocol.py +41 -2
- package/md_cg/provenance.py +22 -4
- package/md_cg/reach.py +4 -4
- package/md_cg/readcache.py +76 -15
- package/md_cg/reconcile.py +1 -1
- package/md_cg/refindex.py +246 -37
- package/md_cg/refine.py +1 -1
- package/md_cg/routing.py +32 -5
- package/md_cg/run_tests.py +17 -0
- package/md_cg/scrub.py +30 -3
- package/md_cg/security.py +47 -1
- package/md_cg/self_state.py +5 -5
- package/md_cg/semantic/canonical.py +22 -0
- package/md_cg/semantic/unify.py +73 -22
- package/md_cg/semantic/unify_fixture.json +25 -0
- package/md_cg/sleep.py +1297 -0
- package/md_cg/sources.py +23 -6
- package/md_cg/srcindex.py +352 -0
- package/md_cg/stg.py +47 -3
- package/md_cg/subgraph.py +2 -2
- package/md_cg/sustain.py +1436 -1168
- package/md_cg/tasks.py +469 -470
- package/md_cg/test_auto_defaults.py +424 -0
- package/md_cg/test_b1_auto_id_multiproc.py +277 -0
- package/md_cg/test_b1b2_write_face.py +417 -0
- package/md_cg/test_b2_sensitivity_landing.py +223 -0
- package/md_cg/test_b3_merge_keeps_content.py +576 -0
- package/md_cg/test_b4_shard_dir_selfheal.py +843 -0
- package/md_cg/test_b4_shard_dir_selfheal_guard.py +238 -0
- package/md_cg/test_boundary_hit.py +410 -0
- package/md_cg/test_c3_transient_read_negative.py +793 -0
- package/md_cg/test_c8_search_rrf_gates.py +502 -0
- package/md_cg/test_ccg_form_parity.py +188 -0
- package/md_cg/test_ccgc.py +28 -3
- package/md_cg/test_en_pipeline.py +22 -13
- package/md_cg/test_generation_guard.py +352 -0
- package/md_cg/test_govern_directread.py +17 -4
- package/md_cg/test_h2_session_view_norm.py +233 -0
- package/md_cg/test_h4_sustain_snapshot.py +1387 -0
- package/md_cg/test_hive_ingest.py +285 -285
- package/md_cg/test_issue39_utf8_stdio.py +307 -16
- package/md_cg/test_issue43_default_policy.py +865 -0
- package/md_cg/test_legacy_p3_node_id_type.py +375 -0
- package/md_cg/test_linkref.py +10 -3
- package/md_cg/test_lock.py +2 -2
- package/md_cg/test_logref.py +1109 -0
- package/md_cg/test_m3_h9_bucket_health_protect_mark.py +301 -0
- package/md_cg/test_m3_h9_semantic_guard.py +525 -0
- package/md_cg/test_mr_m2.py +15 -3
- package/md_cg/test_n130_verify_falsified_protect.py +1 -1
- package/md_cg/test_n139_dek_provision_failclosed.py +185 -0
- package/md_cg/test_n176_link_trust.py +221 -0
- package/md_cg/test_n178_units_jobid_gate.py +212 -0
- package/md_cg/test_n184_keys_concurrent_provision.py +307 -0
- package/md_cg/test_n195_writepath_reload.py +283 -0
- package/md_cg/test_n196_stale_gate_skip.py +169 -0
- package/md_cg/test_n197_n208_write_face_gates.py +471 -0
- package/md_cg/test_n198_tokens_corrupt_failclosed.py +225 -0
- package/md_cg/test_n199_tokens_concurrent_write.py +378 -0
- package/md_cg/test_n201_proposal_visibility.py +382 -0
- package/md_cg/test_n202_session_notes_visibility.py +461 -0
- package/md_cg/test_n204_n205_n226_n227_n228_n229_exit_gates.py +542 -0
- package/md_cg/test_n206_stdio_jsonrpc_type.py +403 -0
- package/md_cg/test_n209_verify_write_face_gates.py +461 -0
- package/md_cg/test_n212_n213_n224_generation_gates.py +519 -0
- package/md_cg/test_n214_n215_n221_n222_write_face_gates.py +563 -0
- package/md_cg/test_n225_nonobject_load.py +1110 -0
- package/md_cg/test_n230_dirty_replay.py +375 -0
- package/md_cg/test_n62_tenant_bind_failclosed.py +200 -0
- package/md_cg/test_neg_condition_hits.py +333 -0
- package/md_cg/test_neg_tail_honesty.py +663 -0
- package/md_cg/test_none_id_write_guard.py +4 -3
- package/md_cg/test_opt_batch1_md_cg.py +451 -0
- package/md_cg/test_p1.py +5 -5
- package/md_cg/test_p11_consistency.py +161 -12
- package/md_cg/test_p26_refindex.py +2 -2
- package/md_cg/test_p27_docindex.py +777 -774
- package/md_cg/test_p28_refcheck.py +740 -13
- package/md_cg/test_p29_session_ingest_export.py +2 -2
- package/md_cg/test_p2_mcp.py +12 -1
- package/md_cg/test_p2_six_elements.py +463 -0
- package/md_cg/test_p30_maintain.py +33 -2
- package/md_cg/test_p31_insight.py +1 -0
- package/md_cg/test_p3_legacy_closure.py +433 -0
- package/md_cg/test_p4_freshness.py +680 -0
- package/md_cg/test_p8_subgraph_chain.py +14 -1
- package/md_cg/test_p9c_dedup_hints.py +276 -0
- package/md_cg/test_policy_required_ccg.py +426 -0
- package/md_cg/test_protocol.py +22 -0
- package/md_cg/test_rank_parity_score_mode.py +522 -0
- package/md_cg/test_read_face_input_gates.py +363 -0
- package/md_cg/test_read_face_semantics.py +489 -0
- package/md_cg/test_recall_face_guards.py +812 -0
- package/md_cg/test_rejected_credential_forms.py +188 -0
- package/md_cg/test_rejected_redact.py +146 -0
- package/md_cg/test_retr_s1b.py +2 -2
- package/md_cg/test_retr_s5.py +20 -7
- package/md_cg/test_review_conformance.py +21 -3
- package/md_cg/test_security_audit_v21.py +2 -2
- package/md_cg/test_semantic_canonical.py +5 -3
- package/md_cg/test_server_version.py +67 -0
- package/md_cg/test_sleep.py +611 -0
- package/md_cg/test_sleep_p1.py +784 -0
- package/md_cg/test_srcindex.py +171 -0
- package/md_cg/test_tenant_env_override_warn.py +63 -50
- package/md_cg/test_time_core_lint.py +968 -0
- package/md_cg/test_token_lowercase_form.py +315 -0
- package/md_cg/test_unify_default_off.py +701 -0
- package/md_cg/test_unify_scope.py +182 -0
- package/md_cg/test_v21r1_package_version.py +310 -0
- package/md_cg/test_writepipe.py +10 -4
- package/md_cg/tokens.py +225 -95
- package/md_cg/tool_face.py +4 -4
- package/md_cg/trust.py +49 -5
- package/md_cg/units.py +204 -6
- package/md_cg/weights.py +4 -4
- package/md_cg/whitebox_kb/aeis_core/time_core.py +8 -0
- package/md_cg/whitebox_kb/wisdom/knowledge_points.py +1 -1
- package/md_cg/writelimit.py +42 -8
- package/md_cg/writepipe.py +651 -554
- package/package.json +4 -2
- package/skills/plugin.json +1 -1
- package/src/bridge.ts +25 -2
- package/src/hooks.ts +229 -16
- package/src/init.ts +15 -1
- package/src/lib/prompt_safety.ts +88 -1
- package/utf8_boot.py +237 -0
- package/zcode/AGENTS.md +10 -10
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""统一归一层作用域收窄守卫(2026-09-30 使用者裁定)
|
|
3
|
+
|
|
4
|
+
口径:`semantic/unify.py::unify_query` **只对英文内容做翻译归一,中文内容
|
|
5
|
+
原样不动**——query 按中文段/非中文段切开,中文段逐字保留、绝不送 segment。
|
|
6
|
+
旧口径「含任一 ASCII 字母即整条归一」把中夹英 query 的中文部分逐字切开
|
|
7
|
+
(「自我接纳」→「自 我 接 纳」),本件钉住收窄后的语义。
|
|
8
|
+
|
|
9
|
+
**开关前提**:归一层缺省**关**(2026-09-30 使用者裁定,唯一开态 = 显式
|
|
10
|
+
`MDCG_UNIFY_QUERY=1`);本件第 1 节钉三态,其后各节一律**显式开**下跑。
|
|
11
|
+
缺省态的三态与两侧一致性守卫见 `md_cg/test_unify_default_off.py`。
|
|
12
|
+
|
|
13
|
+
两侧同源:期望值取自 `md_cg/semantic/unify_fixture.json`——Rust 侧
|
|
14
|
+
`rust/src/atoms.rs::tests::unify_mixed_fixture_matches_python` 用 include_str!
|
|
15
|
+
嵌入**同一份**文件;任一侧漂移即红(两侧逐位相同)。
|
|
16
|
+
|
|
17
|
+
跑法:python -X utf8 -m md_cg.test_unify_scope
|
|
18
|
+
"""
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
|
|
23
|
+
HERE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
24
|
+
if HERE not in sys.path:
|
|
25
|
+
sys.path.insert(0, HERE)
|
|
26
|
+
|
|
27
|
+
from md_cg.semantic.canonical import is_zh_char # noqa: E402
|
|
28
|
+
from md_cg.semantic.unify import unify_on, unify_query # noqa: E402
|
|
29
|
+
|
|
30
|
+
FIXTURE = os.path.join(HERE, "md_cg", "semantic", "unify_fixture.json")
|
|
31
|
+
|
|
32
|
+
N = 0
|
|
33
|
+
BAD = []
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# 生效条件:单参 cond 为真值判定、msg 为说明串;恒累加计数 N,cond 为假时把 msg 记入模块级 BAD(**不中断**)——收集式断言使「定点变异红了几项」可数(首条即停的 assert 风格数不出红项数)。
|
|
37
|
+
def ok(cond, msg):
|
|
38
|
+
global N
|
|
39
|
+
N += 1
|
|
40
|
+
if not cond:
|
|
41
|
+
BAD.append(msg)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# 生效条件:单参 text 为任意字符串,按 canonical.is_zh_char 切出其中的中文连续段,返回保序的段列表(无中文段时为空列表);仅供本守卫做「中文段逐字保留」的结构判据。
|
|
45
|
+
def zh_runs(text):
|
|
46
|
+
"""文本里的中文连续段(判据同 unify_query:canonical.is_zh_char)。"""
|
|
47
|
+
out, buf = [], []
|
|
48
|
+
for ch in text:
|
|
49
|
+
if is_zh_char(ch):
|
|
50
|
+
buf.append(ch)
|
|
51
|
+
elif buf:
|
|
52
|
+
out.append("".join(buf))
|
|
53
|
+
buf = []
|
|
54
|
+
if buf:
|
|
55
|
+
out.append("".join(buf))
|
|
56
|
+
return out
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ---- 1 · 开关三态(2026-09-30 使用者裁定:缺省由开翻为关;唯一开态 = 显式 "1")----
|
|
60
|
+
# 本件测的是**作用域收窄**语义,故从本节之后各节必须在**显式开**(=1)下跑:
|
|
61
|
+
# 缺省已关(未设即关),若不显式开,下面 3~8 节全会退化成恒真(对实现零约束力)。
|
|
62
|
+
# 缺省三态守卫另立一件:md_cg/test_unify_default_off.py(两件分工不同,勿互推)。
|
|
63
|
+
ok(unify_on() is False,
|
|
64
|
+
"MDCG_UNIFY_QUERY 未设即关(缺省关,2026-09-30 翻)")
|
|
65
|
+
os.environ["MDCG_UNIFY_QUERY"] = "0"
|
|
66
|
+
try:
|
|
67
|
+
ok(unify_on() is False, "=0 显式关")
|
|
68
|
+
for t in ("I eat beef yesterday", "领养 LGBTQ 群体", "自我接纳",
|
|
69
|
+
" beef 报告 ", "2024 报告"):
|
|
70
|
+
ok(unify_query(t) == t, "开关关:一律原样返回 %r" % t)
|
|
71
|
+
finally:
|
|
72
|
+
os.environ.pop("MDCG_UNIFY_QUERY", None)
|
|
73
|
+
os.environ["MDCG_UNIFY_QUERY"] = "1"
|
|
74
|
+
ok(unify_on() is True, "=1 显式开(英文对照路要用)——本件余下各节的运行前提")
|
|
75
|
+
|
|
76
|
+
# ---- 2 · 空/None/无 ASCII 字母:原样返回(未 strip 的入参)----
|
|
77
|
+
ok(unify_query(None) is None, "None 原样")
|
|
78
|
+
ok(unify_query("") == "", "空串原样")
|
|
79
|
+
ok(unify_query(" 自我接纳 ") == " 自我接纳 ",
|
|
80
|
+
"纯中文(无 ASCII 字母)原样返回入参本身(不 strip)")
|
|
81
|
+
ok(unify_query("2024 报告") == "2024 报告", "纯中文+数字(无字母)原样")
|
|
82
|
+
ok(unify_query(",。") == ",。", "纯标点(无字母)原样")
|
|
83
|
+
|
|
84
|
+
# ---- 3 · 纯英文仍翻译(收窄不得关掉英文链路)----
|
|
85
|
+
ok(unify_query("I eat beef yesterday") == "我 吃 牛肉 昨天",
|
|
86
|
+
"纯英文仍归一:%r" % unify_query("I eat beef yesterday"))
|
|
87
|
+
ok(unify_query("wrote") == "写", "纯英文时态还原仍生效")
|
|
88
|
+
|
|
89
|
+
# ---- 4 · 纯中文原样(旧口径会被逐字切开)----
|
|
90
|
+
for t, why in (("自我接纳", "旧口径「自 我 接 纳」"),
|
|
91
|
+
("慈善跑", "旧口径「慈 善 跑」"),
|
|
92
|
+
("蜂群调度 依赖门禁", "旧口径逐字切开")):
|
|
93
|
+
ok(unify_query(t) == t, "纯中文原样(%s):%r" % (why, t))
|
|
94
|
+
|
|
95
|
+
# ---- 5 · fixture 逐位对拍(两侧同源:Rust 侧嵌同一份文件)----
|
|
96
|
+
with open(FIXTURE, encoding="utf-8") as f:
|
|
97
|
+
FX = json.load(f)["cases"]
|
|
98
|
+
ok(len(FX) >= 12, "fixture 用例数 >= 12:%d" % len(FX))
|
|
99
|
+
for c in FX:
|
|
100
|
+
got = unify_query(c["in"])
|
|
101
|
+
ok(got == c["out"],
|
|
102
|
+
"fixture 逐位不符 [%s] in=%r got=%r want=%r"
|
|
103
|
+
% (c["name"], c["in"], got, c["out"]))
|
|
104
|
+
|
|
105
|
+
# ---- 5b · 单侧钉:任务原型例(Python 侧形态)----
|
|
106
|
+
# 这两例**不入共享 fixture**:非中文段是未登录的**大写**词(LGBTQ),两侧对
|
|
107
|
+
# 「未命中词表的大写词」存在**既有**非对称(Python 保留原大小写 / Rust 经
|
|
108
|
+
# normalize_en 一律 lower 化——用 rust 单测实测 `领养 lgbtq 群体` 取证,
|
|
109
|
+
# 2026-09-30),故两侧钉不出同一期望值。此非本次收窄引入,也非本次范围;
|
|
110
|
+
# 但它是端到端(Python 侧)实际生效的形态,故在此单侧钉死。
|
|
111
|
+
for t in ("领养 LGBTQ 群体 支持 包容", "领养 机构 LGBTQ 群体 支持 包容"):
|
|
112
|
+
ok(unify_query(t) == t, "中夹英原型例:中文段逐字保留、英文片段原样:%r" % t)
|
|
113
|
+
|
|
114
|
+
# ---- 6 · 结构判据(对**实现产物**断言):混合 query 的中文段逐字保留 ----
|
|
115
|
+
# 2026-09-30 清理批次(甲2)修假守卫:本节此前比的是 fixture 的**期望值**
|
|
116
|
+
# (`run in c["out"]`——拿期望比期望),对实现零约束力:做「中文段也送
|
|
117
|
+
# segment」变异时本节 0 项红。现改为直接调 `unify_query` 并对**产物**断言:
|
|
118
|
+
# 中文段必须在产物里逐字出现,且其逐字切开形(`" ".join(run)`)不得出现。
|
|
119
|
+
# 单字中文段(如「年」)的切开形与其本身同形,故切开判据只在 len(run) > 1
|
|
120
|
+
# 时施加(否则是恒假的伪红)。
|
|
121
|
+
for c in FX:
|
|
122
|
+
got = unify_query(c["in"])
|
|
123
|
+
for run in zh_runs(c["in"]):
|
|
124
|
+
ok(run in got,
|
|
125
|
+
"中文段未逐字保留(被改写)[%s] in=%r 段=%r got=%r"
|
|
126
|
+
% (c["name"], c["in"], run, got))
|
|
127
|
+
if len(run) > 1:
|
|
128
|
+
ok(" ".join(run) not in got,
|
|
129
|
+
"中文段被逐字切开(送了 segment)[%s] in=%r 段=%r got=%r"
|
|
130
|
+
% (c["name"], c["in"], run, got))
|
|
131
|
+
# 原型例(任务口径原句)同样对产物断言:中文段逐字保留、英文片段不动
|
|
132
|
+
for t in ("领养 LGBTQ 群体", "AI 记忆 系统", "自我接纳", "蜂群调度 依赖门禁"):
|
|
133
|
+
got = unify_query(t)
|
|
134
|
+
for run in zh_runs(t):
|
|
135
|
+
ok(run in got and " ".join(run) not in got,
|
|
136
|
+
"原型例中文段逐字保留 [%r] 段=%r got=%r" % (t, run, got))
|
|
137
|
+
ok(zh_runs("领养 LGBTQ 群体") == ["领养", "群体"], "中文段抽取判据自检")
|
|
138
|
+
|
|
139
|
+
# ---- 7 · 空白折叠为单空格(段内/段间)----
|
|
140
|
+
ok(unify_query(" beef 报告 ") == "牛肉 报告", "前后空白+多空格折叠")
|
|
141
|
+
ok(unify_query("beef\t报告") == "牛肉 报告", "制表符折叠为单空格")
|
|
142
|
+
|
|
143
|
+
# ---- 8 · 异常降级为原样(不阻断检索主链路)----
|
|
144
|
+
import md_cg.semantic.canonical as _cn # noqa: E402
|
|
145
|
+
_saved = _cn.query_atoms
|
|
146
|
+
_cn.query_atoms = lambda _t: (_ for _ in ()).throw(RuntimeError("boom"))
|
|
147
|
+
try:
|
|
148
|
+
src = "beef 报告"
|
|
149
|
+
ok(unify_query(src) == src, "链路抛异常 → 原样返回")
|
|
150
|
+
finally:
|
|
151
|
+
_cn.query_atoms = _saved
|
|
152
|
+
|
|
153
|
+
# ---- 9 · 中文判据边界(2026-09-30 清理批次乙2):仅长度恰为 1 的串可为真 ----
|
|
154
|
+
# 事实读数(本机实测,见清理批次回执):留池条目 ⑥ 记「`is_zh_char("")` 返回
|
|
155
|
+
# True」——**实测不成立**:`ZH_LO <= ch <= ZH_HI` 是链式比较,对空串即假,
|
|
156
|
+
# 收紧前 `is_zh_char("")` 已返回 False。真实外溢面是**多字符串**——
|
|
157
|
+
# 收紧前 `is_zh_char("中文")` 判真(首字符在区间即真)。两处消费点
|
|
158
|
+
# (unify._runs / 本文件 zh_runs)均逐字符传入,故本收紧对存量行为零变更,
|
|
159
|
+
# 只把判据面(长度恰为 1)钉死。
|
|
160
|
+
ok(is_zh_char("") is False, "空串不是中文(判据面外)")
|
|
161
|
+
ok(is_zh_char("中文") is False, "多字符串为假(首字符在区间亦然)")
|
|
162
|
+
ok(is_zh_char("中a") is False, "多字符串为假(汉字+ASCII 混合)")
|
|
163
|
+
ok(is_zh_char("中") is True, "单汉字为真")
|
|
164
|
+
ok(is_zh_char("\u4e00") is True, "区间下界单汉字为真")
|
|
165
|
+
ok(is_zh_char("\u9fff") is True, "区间上界单汉字为真")
|
|
166
|
+
ok(is_zh_char("\u4dff") is False, "区间下界外单字符为假(U+4DFF)")
|
|
167
|
+
ok(is_zh_char("\ua000") is False, "区间上界外单字符为假(U+A000)")
|
|
168
|
+
ok(is_zh_char("a") is False, "单 ASCII 字母为假")
|
|
169
|
+
# 两侧边界语义一致性:Rust `text::is_zh` 收 char,天然无空/多字符二态;
|
|
170
|
+
# 本侧判据收紧后,「长度 != 1 一律 False」与「按 char 判区间」等价
|
|
171
|
+
# (见 rust/src/text.rs::is_zh docstring 的边界约定段)。
|
|
172
|
+
for ch in ("\u4e00", "\u9fff"):
|
|
173
|
+
ok(is_zh_char(ch) and len(ch) == 1, "单字符区间内 → 真(与 Rust is_zh 等价)%r" % ch)
|
|
174
|
+
|
|
175
|
+
os.environ.pop("MDCG_UNIFY_QUERY", None) # 归还进程环境(本件全程已显式开完)
|
|
176
|
+
|
|
177
|
+
if BAD:
|
|
178
|
+
print("[test_unify_scope] FAILED %d/%d 断言:" % (len(BAD), N))
|
|
179
|
+
for b in BAD:
|
|
180
|
+
print(" - " + b)
|
|
181
|
+
raise SystemExit(1)
|
|
182
|
+
print("[test_unify_scope] %d 断言全绿(fixture %d 例,两侧同源)" % (N, len(FX)))
|
|
@@ -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())
|
package/md_cg/test_writepipe.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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))
|