@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.
- package/README.md +588 -552
- package/codebuddy/CODEBUDDY.md +10 -10
- package/data/policy.json +27 -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/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
- 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
- package/docs/eval/DSH/347/253/257/347/274/272/351/231/267/344/270/223/351/241/271_v1.0.md +254 -0
- 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/P1b2_/350/257/273/351/235/242/344/273/243/351/231/205/344/277/256/345/244/215_v1.0.md +124 -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/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
- 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//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_v18.md +183 -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_v19.md +207 -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/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
- 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 +12 -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 +21 -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//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//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/dsh/cordis-patch-profile-web.example.yml +35 -0
- package/lib/bridge.js +24 -2
- package/lib/cli.d.ts +3 -0
- package/lib/cli.js +66 -0
- package/lib/hooks.d.ts +31 -2
- package/lib/hooks.js +219 -16
- package/lib/init.d.ts +88 -0
- package/lib/init.js +302 -0
- 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_p0.py +1 -1
- package/md_cg/branches.py +2 -2
- package/md_cg/ccgc.py +1071 -1006
- package/md_cg/chain.py +1 -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/evidence.py +585 -582
- package/md_cg/evolution.py +17 -1
- package/md_cg/export.py +1 -1
- package/md_cg/forgetting.py +309 -13
- package/md_cg/fsutil.py +454 -8
- 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 +4152 -3818
- package/md_cg/mdcg.py +4208 -3462
- package/md_cg/mdcos.py +4491 -4137
- package/md_cg/mreview/pipeline.py +15 -1
- package/md_cg/nodefile.py +639 -575
- 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/review_cli.py +49 -4
- 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/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 +1299 -1168
- package/md_cg/tasks.py +469 -470
- 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_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_govern_directread.py +17 -4
- package/md_cg/test_h2_session_view_norm.py +233 -0
- package/md_cg/test_h4_sustain_snapshot.py +1377 -0
- package/md_cg/test_hive_ingest.py +285 -285
- package/md_cg/test_index_crossprocess_reload.py +301 -0
- 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 +513 -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_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 +166 -0
- 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_p30_maintain.py +33 -2
- package/md_cg/test_p31_insight.py +1 -0
- 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 +516 -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_cli_attribution.py +177 -0
- package/md_cg/test_review_cli_visibility.py +235 -0
- package/md_cg/test_review_conformance.py +21 -3
- package/md_cg/test_security_audit_v21.py +2 -2
- package/md_cg/test_server_version.py +67 -0
- package/md_cg/test_srcindex.py +171 -0
- package/md_cg/test_tenant_env_override_warn.py +63 -50
- package/md_cg/test_token_lowercase_form.py +315 -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/wisdom/knowledge_points.py +1 -1
- package/md_cg/writelimit.py +42 -8
- package/md_cg/writepipe.py +651 -554
- package/package.json +8 -2
- package/skills/plugin.json +1 -1
- package/src/bridge.ts +25 -2
- package/src/cli.ts +65 -0
- package/src/hooks.ts +229 -16
- package/src/init.ts +361 -0
- package/src/lib/prompt_safety.ts +88 -1
- package/zcode/AGENTS.md +10 -10
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# 功能名:读面输入语义守卫(M1 显式 k 的**条数**语义 / M2 node_id 类型错与不存在的**可区分性** /
|
|
3
|
+
# M4 action 来源**恒定在场**)
|
|
4
|
+
# 生效条件:md_cg/mcp_server.py 的 `_int_arg`(六处委托点:read 搜索路 :2291、read 召回路 :2284、
|
|
5
|
+
# mdcg_search :3343、mdcg_recall :3330、pool_measure :1610、pool_compare :1620)、
|
|
6
|
+
# read 分支的 `node_id` 类型闸(:2271-2278)、`_cg_call` 的
|
|
7
|
+
# `out.setdefault("action_source", act_source)`(:1849-1857)与 `_ACTION_DEFAULT`
|
|
8
|
+
# (:1763)及其 `_cg_call.__doc__` 文档表同源时成立;
|
|
9
|
+
# 沙箱条件:库根一律 tempfile.mkdtemp,绝不触在役库、绝不重启在役服务。
|
|
10
|
+
# 子功能:
|
|
11
|
+
# M1 条数语义:显式 0 / False → **0 条**(修前回落 20);缺省 / 显式 None → 20 条;
|
|
12
|
+
# k=3 → 3 条;k=100 → 全量;六处入口逐一验证;pooling 的 `k` 读数如实回显;
|
|
13
|
+
# 「k<=0 意味什么」的判据单点在库层(本层不另造报错)。
|
|
14
|
+
# M2 类型闸:非 str node_id(int/float/bool/bytes/list/dict/tuple/set/object)→ 结构化
|
|
15
|
+
# `{"ok": False, "error": "node_id_not_str", "got_type": ...}`,不抛异常(修前哈希可算者
|
|
16
|
+
# 返回**裸 null**、不可哈希者 TypeError 逃出);「id 不存在」仍是协议登记的 null;
|
|
17
|
+
# 两态可区分;空串也是「显式」;与字符串形态 id 的碰撞面不得被静默取到。
|
|
18
|
+
# M4 来源透出:`action_source` 四态(explicit/sig/default/none)取值正确、与实际**执行**的
|
|
19
|
+
# action 逐字一致、全 op 恒定在场;`_ACTION_DEFAULT` 与 `_cg_call` 文档表逐项同源。
|
|
20
|
+
# 执行:python -X utf8 -m md_cg.test_read_face_semantics
|
|
21
|
+
# 验证方式:三组断言各自打**目标语义**(条数 / 两态可区分 / 来源与实际执行一致),经 MCP 面
|
|
22
|
+
# (_cg_call / _dispatch / _sustain_call)在临时沙箱读数。定点变异自证:
|
|
23
|
+
# `python -X utf8 -m md_cg.test_read_face_semantics --drop-fix=all`
|
|
24
|
+
# ——在内存里把 mcp_server.py 源码退回改动前形态(只 exec 副本,**不写盘**),
|
|
25
|
+
# 要求指定断言变红、恢复后转绿。
|
|
26
|
+
# 不适用条件:不覆盖写面 node_id 类型闸(md_cg/mdcg.py:1776 的 ValueError);不覆盖「节点不存在 →
|
|
27
|
+
# null」的协议形态(真源 md_cg/protocol.py 的 node_missing.returns_null,反向证据
|
|
28
|
+
# md_cg/test_protocol.py A11/B8/B10);不覆盖细粒度工具(mdcg_get 等)的入参;不覆盖
|
|
29
|
+
# k 之外的同族入参(limit/offset/sample 等仍为 `or` 回落,非本次修复面)。
|
|
30
|
+
"""M1 / M2 / M4 读面输入语义守卫 —— 断言打在语义上,不打在站点上。
|
|
31
|
+
|
|
32
|
+
三个读面缺陷(2026-09-30 修复,真源 md_cg/mcp_server.py):
|
|
33
|
+
|
|
34
|
+
M1 · 显式 k=0 被 `int(a.get("k") or 20)` 当成「没传」→ 静默给 20 条。本文件断言的**语义**是
|
|
35
|
+
「调用方要 0 条就得到 0 条、要 k 条就最多 k 条」,而不是「某一行改成了什么写法」。
|
|
36
|
+
|
|
37
|
+
M2 · 非 str 的 node_id 静默失真(裸 null / TypeError)。本文件断言的**语义**是「类型错必须
|
|
38
|
+
与『id 不存在』可区分,且不得抛穿 MCP 面、不得降级为检索、不得取到别的节点」。
|
|
39
|
+
|
|
40
|
+
M4 · 显式 action 时返回里无任何来源痕迹。本文件断言的**语义**是「调用方无需猜:返回里恒有
|
|
41
|
+
action_source,且它与实际执行的那条 action 逐字一致」。
|
|
42
|
+
|
|
43
|
+
下游消费面(改动影响谁):`action_source` 供经 MCP 面调 cg 的 agent/客户端判断 action 来源;
|
|
44
|
+
`k` 决定 read 的 results/pack 条数、mdcg_search/mdcg_recall 的返回条数、pool_measure/compare
|
|
45
|
+
回显的 `k` 字段;`node_id` 的两态形态供调用方区分「传错类型」与「id 不存在」。
|
|
46
|
+
"""
|
|
47
|
+
import os
|
|
48
|
+
import re
|
|
49
|
+
import shutil
|
|
50
|
+
import sys
|
|
51
|
+
import tempfile
|
|
52
|
+
import types
|
|
53
|
+
|
|
54
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
55
|
+
|
|
56
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
57
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
58
|
+
|
|
59
|
+
from md_cg import mcp_server as M
|
|
60
|
+
from md_cg.mdcos import MdCGSecure
|
|
61
|
+
from md_cg.security import DEFAULT_SENSITIVITY, Principal
|
|
62
|
+
|
|
63
|
+
PASS = 0
|
|
64
|
+
FAILS = []
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def check(name, cond, detail=""):
|
|
68
|
+
global PASS
|
|
69
|
+
if cond:
|
|
70
|
+
PASS += 1
|
|
71
|
+
print("[PASS] " + name)
|
|
72
|
+
else:
|
|
73
|
+
FAILS.append(name)
|
|
74
|
+
print("[FAIL] %s · %s" % (name, str(detail)[:240]))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ---------------- 取值钳(修前形态会返回裸 null / 抛 TypeError,直接 .get 会拖崩文件) --------
|
|
78
|
+
def _g(o, key, default=None):
|
|
79
|
+
return o.get(key, default) if isinstance(o, dict) else default
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _n(o, key):
|
|
83
|
+
v = _g(o, key)
|
|
84
|
+
return len(v) if isinstance(v, (list, tuple, dict)) else None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _call(fn, *a, **kw):
|
|
88
|
+
"""调 MCP 面并吞异常为可读 dict:异常本身是「形状错」,由断言判红而非拖崩文件。"""
|
|
89
|
+
try:
|
|
90
|
+
return fn(*a, **kw)
|
|
91
|
+
except Exception as e: # noqa: BLE001
|
|
92
|
+
return {"_raised": "%s: %s" % (type(e).__name__, e)}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class Surface(object):
|
|
96
|
+
"""被测面:真模块或「源码退回改动前」的内存副本,断言只经它取函数。"""
|
|
97
|
+
|
|
98
|
+
def __init__(self, mod):
|
|
99
|
+
self.mod = mod
|
|
100
|
+
self._cg_call = mod._cg_call
|
|
101
|
+
self._dispatch = mod._dispatch
|
|
102
|
+
self._sustain_call = mod._sustain_call
|
|
103
|
+
self._node_view = mod._node_view
|
|
104
|
+
self._int_arg = mod._int_arg
|
|
105
|
+
self._ACTION_DEFAULT = mod._ACTION_DEFAULT
|
|
106
|
+
self.doc = mod._cg_call.__doc__ or ""
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _body(i, token="红按钮"):
|
|
110
|
+
"""六要素齐备的语料(过 CCG 闸),每条互不相同以免被去重/合并。"""
|
|
111
|
+
return ("# 功能名:%s移动%d\n# 生效条件:问%s%d\n# 子功能:左移%d\n"
|
|
112
|
+
"# 执行:%s控制角色左移%d\n# 验证方式:test\n# 不适用条件:问蓝按钮%d\n\n"
|
|
113
|
+
"%s控制角色左移 探针编号%d\n" % (token, i, token, i, i, token, i, i, token, i))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _mk_cg(prefix):
|
|
117
|
+
root = tempfile.mkdtemp(prefix=prefix)
|
|
118
|
+
p = Principal(actor="tester", clearance=DEFAULT_SENSITIVITY, can_write=True,
|
|
119
|
+
can_admin=True, role="designer", session="sess_s",
|
|
120
|
+
harness="test-harness")
|
|
121
|
+
return MdCGSecure(root, principal=p), root
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# ==========================================================================
|
|
125
|
+
# M1 · 条数语义:显式 k 是被尊重的预算(缺省才是 20)
|
|
126
|
+
# ==========================================================================
|
|
127
|
+
def group_m1(S):
|
|
128
|
+
cg, root = _mk_cg("mdcg_rfs_m1_")
|
|
129
|
+
try:
|
|
130
|
+
N = 25
|
|
131
|
+
for i in range(N):
|
|
132
|
+
cg.add("probe_%02d" % i, _body(i), layer="knowledge",
|
|
133
|
+
verification_basis="test")
|
|
134
|
+
self_cg = len(cg.index["nodes"]) == N
|
|
135
|
+
check("M1-0 语料自检:沙箱内 %d 条命中语料就位" % N, self_cg,
|
|
136
|
+
len(cg.index["nodes"]))
|
|
137
|
+
o = _call(S._cg_call, cg, {"op": "read", "query": "红按钮", "k": 100})
|
|
138
|
+
check("M1-0b 语料自检:k=100 能收回全量 %d 条(否则条数断言不可判)" % N,
|
|
139
|
+
_n(o, "results") == N, "results=%s" % _n(o, "results"))
|
|
140
|
+
|
|
141
|
+
# —— 诚实化核心:显式 0/False 必须得到 0 条(修前回落 20) ——
|
|
142
|
+
for kk in (0, False):
|
|
143
|
+
o = _call(S._cg_call, cg, {"op": "read", "query": "红按钮", "k": kk})
|
|
144
|
+
check("M1-1 read+query 显式 k=%r → 0 条(修前静默给默认 20)" % (kk,),
|
|
145
|
+
_n(o, "results") == 0, "results=%s" % _n(o, "results"))
|
|
146
|
+
# —— 不放宽:缺省/显式 None 仍是 20 ——
|
|
147
|
+
for a, tag in (({"op": "read", "query": "红按钮"}, "缺省"),
|
|
148
|
+
({"op": "read", "query": "红按钮", "k": None}, "显式 None")):
|
|
149
|
+
o = _call(S._cg_call, cg, a)
|
|
150
|
+
check("M1-2 %s k → 20 条(默认预算未被改坏)" % tag,
|
|
151
|
+
_n(o, "results") == 20, "results=%s" % _n(o, "results"))
|
|
152
|
+
# —— 条数不超过 k ——
|
|
153
|
+
o = _call(S._cg_call, cg, {"op": "read", "query": "红按钮", "k": 3})
|
|
154
|
+
check("M1-3 read+query k=3 → 3 条(条数不超过 k)", _n(o, "results") == 3,
|
|
155
|
+
"results=%s" % _n(o, "results"))
|
|
156
|
+
o = _call(S._cg_call, cg, {"op": "read", "query": "红按钮", "k": "3"})
|
|
157
|
+
check("M1-4 字符串 k='3' 与整数同宽 → 3 条(不回归)",
|
|
158
|
+
_n(o, "results") == 3, "results=%s" % _n(o, "results"))
|
|
159
|
+
# —— 召回路(budget_tokens):k=0 → pack 0 条 ——
|
|
160
|
+
for kk in (0, False):
|
|
161
|
+
o = _call(S._cg_call, cg, {"op": "read", "query": "红按钮",
|
|
162
|
+
"budget_tokens": 1000000, "k": kk})
|
|
163
|
+
check("M1-5 read+budget_tokens 显式 k=%r → pack 0 条(修前满额 20)"
|
|
164
|
+
% (kk,), _n(o, "pack") == 0, "pack=%s" % _n(o, "pack"))
|
|
165
|
+
# —— 细粒度工具面两条腿 ——
|
|
166
|
+
o = _call(S._dispatch, cg, "mdcg_search", {"query": "红按钮", "k": 0})
|
|
167
|
+
check("M1-6 mdcg_search k=0 → 0 条(修前 20)", _n(o, "results") == 0,
|
|
168
|
+
"results=%s" % _n(o, "results"))
|
|
169
|
+
o = _call(S._dispatch, cg, "mdcg_recall",
|
|
170
|
+
{"query": "红按钮", "budget_tokens": 1000000, "k": 0})
|
|
171
|
+
check("M1-7 mdcg_recall k=0 → pack 0 条(修前 20)", _n(o, "pack") == 0,
|
|
172
|
+
"pack=%s" % _n(o, "pack"))
|
|
173
|
+
# —— pooling 两条腿:读数如实回显 + 端到端确实空入榜 ——
|
|
174
|
+
o = _call(S._sustain_call, cg, {"op": "sustain", "action": "pool_measure",
|
|
175
|
+
"queries": ["红按钮"], "k": 0})
|
|
176
|
+
check("M1-8 pool_measure k=0 → 读数如实回显 k=0(修前回显 20)",
|
|
177
|
+
_g(o, "k") == 0, "k=%r" % _g(o, "k"))
|
|
178
|
+
check("M1-8b pool_measure k=0 → 入榜构成全 0(该查询确实没有任何结果入榜)",
|
|
179
|
+
_g(o, "knowledge_share") == 0.0, "knowledge_share=%r"
|
|
180
|
+
% _g(o, "knowledge_share"))
|
|
181
|
+
o = _call(S._sustain_call, cg, {"op": "sustain", "action": "pool_measure",
|
|
182
|
+
"queries": ["红按钮"]})
|
|
183
|
+
check("M1-8c pool_measure 缺省 → 回显 20 且入榜构成非 0(不回归)",
|
|
184
|
+
_g(o, "k") == 20 and (_g(o, "knowledge_share") or 0) > 0,
|
|
185
|
+
"k=%r share=%r" % (_g(o, "k"), _g(o, "knowledge_share")))
|
|
186
|
+
o = _call(S._sustain_call, cg, {"op": "sustain", "action": "pool_compare",
|
|
187
|
+
"queries": ["红按钮"], "k": 0})
|
|
188
|
+
check("M1-9 pool_compare k=0 → 读数如实回显 k=0",
|
|
189
|
+
_g(o, "k") == 0, "k=%r" % _g(o, "k"))
|
|
190
|
+
# —— 判据单点在库层:本层不另造报错,两处读数同口径 ——
|
|
191
|
+
res, _meta = cg.search("红按钮", k=0)
|
|
192
|
+
check("M1-10 库层 cg.search(k=0) 返空且不抛(本层不另造报错的依据)",
|
|
193
|
+
res == [], res)
|
|
194
|
+
check("M1-11 单点判据 _int_arg 自身口径:显式 0 透传、缺键回落默认",
|
|
195
|
+
S._int_arg({"k": 0}, "k", 20) == 0
|
|
196
|
+
and S._int_arg({}, "k", 20) == 20
|
|
197
|
+
and S._int_arg({"k": None}, "k", 20) == 20,
|
|
198
|
+
"%r/%r/%r" % (S._int_arg({"k": 0}, "k", 20),
|
|
199
|
+
S._int_arg({}, "k", 20), S._int_arg({"k": None}, "k", 20)))
|
|
200
|
+
finally:
|
|
201
|
+
shutil.rmtree(root, ignore_errors=True)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# ==========================================================================
|
|
205
|
+
# M2 · node_id 类型错与「id 不存在」必须可区分,且不得抛穿 / 不得降级为检索
|
|
206
|
+
# ==========================================================================
|
|
207
|
+
_BAD_TYPES = ((123, "int"), (1, "int"), (3.5, "float"), (True, "bool"),
|
|
208
|
+
(False, "bool"), (b"n1", "bytes"), (["x"], "list"), ({"a": 1}, "dict"),
|
|
209
|
+
(("x",), "tuple"), ({"x"}, "set"))
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def group_m2(S):
|
|
213
|
+
cg, root = _mk_cg("mdcg_rfs_m2_")
|
|
214
|
+
try:
|
|
215
|
+
cg.add("n1", _body(1), layer="knowledge", verification_basis="test")
|
|
216
|
+
# 碰撞面:库里真的有字符串形态 id "1" / "True"(与 int 1 / bool True 同形)
|
|
217
|
+
cg.add("1", _body(2), layer="knowledge")
|
|
218
|
+
cg.add("True", _body(3), layer="knowledge")
|
|
219
|
+
|
|
220
|
+
# —— 零回归:str 正路径 ——
|
|
221
|
+
o = _call(S._cg_call, cg, {"op": "read", "node_id": "n1"})
|
|
222
|
+
check("M2-1 str node_id 仍返回节点视图(不回归)",
|
|
223
|
+
_g(o, "id") == "n1" and "content" in (o if isinstance(o, dict) else {}),
|
|
224
|
+
str(o)[:90])
|
|
225
|
+
o = _call(S._cg_call, cg, {"op": "read", "node_id": "n1", "offset": 1})
|
|
226
|
+
check("M2-1b str node_id 带 offset 仍工作(不回归)",
|
|
227
|
+
isinstance(o, dict) and "content" in o, str(o)[:90])
|
|
228
|
+
miss = _call(S._cg_call, cg, {"op": "read", "node_id": "no_such_node"})
|
|
229
|
+
check("M2-2 id 不存在 → 协议登记的 null(protocol/test_protocol 零回归)",
|
|
230
|
+
miss is None, repr(miss)[:120])
|
|
231
|
+
|
|
232
|
+
# —— 类型错:不抛、给结构化 error、带 got_type ——
|
|
233
|
+
for v, tn in _BAD_TYPES:
|
|
234
|
+
r = _call(S._cg_call, cg, {"op": "read", "node_id": v})
|
|
235
|
+
check("M2-3 非 str node_id(%s) 不抛异常(修前 list/dict/set 抛 TypeError)"
|
|
236
|
+
% tn, "_raised" not in (r if isinstance(r, dict) else {}),
|
|
237
|
+
str(r)[:140])
|
|
238
|
+
check("M2-4 非 str node_id(%s) → 结构化 node_id_not_str(修前裸 null)" % tn,
|
|
239
|
+
isinstance(r, dict) and r.get("ok") is False
|
|
240
|
+
and r.get("error") == "node_id_not_str"
|
|
241
|
+
and r.get("got_type") == tn and r.get("node_id") == v,
|
|
242
|
+
str(r)[:140])
|
|
243
|
+
|
|
244
|
+
# —— 两态可区分:类型错 = 结构化 error / 不存在 = null ——
|
|
245
|
+
check("M2-5 两态可区分:类型错是 error 对象、不存在是 null(调用方不必猜)",
|
|
246
|
+
_g(_call(S._cg_call, cg, {"op": "read", "node_id": 1}), "error")
|
|
247
|
+
== "node_id_not_str"
|
|
248
|
+
and _call(S._cg_call, cg, {"op": "read", "node_id": "zzz"}) is None,
|
|
249
|
+
"%r / %r" % (_call(S._cg_call, cg, {"op": "read", "node_id": 1}),
|
|
250
|
+
_call(S._cg_call, cg, {"op": "read", "node_id": "zzz"})))
|
|
251
|
+
# —— 碰撞面:库内真有 id=="1",int 1 不得被静默取到那张卡 ——
|
|
252
|
+
check("M2-6 对照:库里 id=='1' 的节点确实存在(碰撞面真实)",
|
|
253
|
+
_g(cg.get("1"), "id") == "1", repr(cg.get("1"))[:60])
|
|
254
|
+
r = _call(S._cg_call, cg, {"op": "read", "node_id": 1})
|
|
255
|
+
check("M2-7 库里存在 id=='1' 时传 int 1 不得取到它(必须报类型错)",
|
|
256
|
+
_g(r, "error") == "node_id_not_str" and _g(r, "id") != "1", str(r)[:120])
|
|
257
|
+
r = _call(S._cg_call, cg, {"op": "read", "node_id": True})
|
|
258
|
+
check("M2-7b 库里存在 id=='True' 时传 bool True 不得取到它(必须报类型错)",
|
|
259
|
+
_g(r, "error") == "node_id_not_str" and _g(r, "id") != "True",
|
|
260
|
+
str(r)[:120])
|
|
261
|
+
# —— 空串也是「显式」:不得静默降级为检索 ——
|
|
262
|
+
e = _call(S._cg_call, cg, {"op": "read", "node_id": ""})
|
|
263
|
+
check("M2-8 空串 node_id 不再静默降级为检索(无 results/meta)",
|
|
264
|
+
not isinstance(e, dict) or ("results" not in e and "meta" not in e),
|
|
265
|
+
str(e)[:140])
|
|
266
|
+
check("M2-8b 空串走「id 不存在」路(与 M2-2 同形:null)", e is None,
|
|
267
|
+
repr(e)[:120])
|
|
268
|
+
# —— 零回归:显式 None 仍是「未传」→ 检索 ——
|
|
269
|
+
o = _call(S._cg_call, cg, {"op": "read", "node_id": None, "query": "红按钮"})
|
|
270
|
+
check("M2-9 node_id=None 仍是「未传」→ 走检索(不回归)",
|
|
271
|
+
(_n(o, "results") or 0) > 0, str(o)[:100])
|
|
272
|
+
finally:
|
|
273
|
+
shutil.rmtree(root, ignore_errors=True)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
# ==========================================================================
|
|
277
|
+
# M4 · action 来源恒定在场,且与实际执行的 action 逐字一致
|
|
278
|
+
# ==========================================================================
|
|
279
|
+
def _op_enum(S):
|
|
280
|
+
"""从模块**自己的** cg 工具 schema 里取 op 枚举(不另抄一份清单)。"""
|
|
281
|
+
for t in getattr(S.mod, "KERNEL_TOOLS", []):
|
|
282
|
+
if t.get("name") == "cg":
|
|
283
|
+
for v in (t.get("inputSchema") or {}).get("properties", {}).values():
|
|
284
|
+
d = v.get("description") or ""
|
|
285
|
+
if "route|read|write" in d:
|
|
286
|
+
return d.split("|")
|
|
287
|
+
return []
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def group_m4(S):
|
|
291
|
+
cg, root = _mk_cg("mdcg_rfs_m4_")
|
|
292
|
+
try:
|
|
293
|
+
cg.add("n1", _body(1), layer="knowledge", verification_basis="test")
|
|
294
|
+
OPS = _op_enum(S)
|
|
295
|
+
check("M4-0 从模块自身 schema 取到 op 枚举(%d 个)" % len(OPS),
|
|
296
|
+
len(OPS) >= 30, OPS)
|
|
297
|
+
|
|
298
|
+
# —— 文档表 ↔ 常量同源(调用方照文档读到的默认动作表不得漂移) ——
|
|
299
|
+
pairs = set(re.findall(r"([a-z_]+)→([a-z_]+)", S.doc))
|
|
300
|
+
table = set(S._ACTION_DEFAULT.items())
|
|
301
|
+
check("M4-1 _cg_call 文档的默认动作表与 _ACTION_DEFAULT 逐项同源(缺/多均红)",
|
|
302
|
+
pairs == table,
|
|
303
|
+
"缺=%s 多=%s" % (sorted(table - pairs), sorted(pairs - table)))
|
|
304
|
+
|
|
305
|
+
# —— 四态取值 ——
|
|
306
|
+
o = _call(S._cg_call, cg, {"op": "review", "action": "list"})
|
|
307
|
+
check("M4-2 显式 action(同默认)→ action_source='explicit' 且真的执行了该 action",
|
|
308
|
+
_g(o, "action_source") == "explicit" and "pending" in (o or {}),
|
|
309
|
+
str(o)[:120])
|
|
310
|
+
check("M4-2b 显式态下 action_derived 不出现(旧契约保持)",
|
|
311
|
+
isinstance(o, dict) and "action_derived" not in o, sorted(o or {})[:12])
|
|
312
|
+
# 显式 action ≠ 默认 action:载荷必须是显式那条(证明没被默认顶掉),来源也必须如实
|
|
313
|
+
o = _call(S._cg_call, cg, {"op": "theory", "action": "catalog"})
|
|
314
|
+
check("M4-2c 显式 action≠默认(theory catalog vs check)→ 执行的是显式的、来源如实",
|
|
315
|
+
_g(o, "action_source") == "explicit" and "schema" in (o or {})
|
|
316
|
+
and "declaration_hash" not in (o or {}), str(o)[:120])
|
|
317
|
+
_o, _pid = None, None
|
|
318
|
+
pr = _call(S._dispatch, cg, "mdcg_propose",
|
|
319
|
+
{"node_id": "p_act", "content": _body(9)})
|
|
320
|
+
_pid = _g(pr, "pid")
|
|
321
|
+
sig = _call(S._cg_call, cg, {"op": "review", "pid": _pid,
|
|
322
|
+
"decision": "accept", "reason": "读面语义守卫"})
|
|
323
|
+
check("M4-3 参数签名推导 → 'sig' + action_derived + hint_action 点名依据键",
|
|
324
|
+
_g(sig, "action_source") == "sig" and _g(sig, "action_derived") is True
|
|
325
|
+
and bool(_g(sig, "hint_action")), str(sig)[:140])
|
|
326
|
+
o = _call(S._cg_call, cg, {"op": "review"})
|
|
327
|
+
check("M4-4 无签名走默认 → 'default' 且 action == _ACTION_DEFAULT[op]",
|
|
328
|
+
_g(o, "action_source") == "default"
|
|
329
|
+
and _g(o, "action") == S._ACTION_DEFAULT["review"],
|
|
330
|
+
str({k: _g(o, k) for k in ("action", "action_source")}))
|
|
331
|
+
for _op in ("read", "status", "route", "info", "edges", "help"):
|
|
332
|
+
o = _call(S._cg_call, cg, {"op": _op})
|
|
333
|
+
check("M4-5 无默认的 op=%s → 'none'(不编造默认)" % _op,
|
|
334
|
+
_g(o, "action_source") == "none"
|
|
335
|
+
and _g(o, "action_derived") is not True,
|
|
336
|
+
str({k: _g(o, k) for k in ("action", "action_source",
|
|
337
|
+
"action_derived")}))
|
|
338
|
+
# —— 全表逐项:默认动作**实际执行**的那条 == 层里声明的默认 ——
|
|
339
|
+
for _op in sorted(S._ACTION_DEFAULT):
|
|
340
|
+
o = _call(S._cg_call, cg, {"op": _op})
|
|
341
|
+
_d = S._ACTION_DEFAULT[_op]
|
|
342
|
+
check("M4-6 op=%-13s 缺省 → source=default / action=%s(与实际执行一致)"
|
|
343
|
+
% (_op, _d),
|
|
344
|
+
_g(o, "action_source") == "default" and _g(o, "action") == _d
|
|
345
|
+
and _g(o, "action_derived") is True and bool(_g(o, "hint_action")),
|
|
346
|
+
str({k: _g(o, k) for k in ("action", "action_source",
|
|
347
|
+
"action_derived")})[:120])
|
|
348
|
+
# —— 恒定在场:模块自身 op 枚举全覆盖 ——
|
|
349
|
+
raised = []
|
|
350
|
+
for _op in OPS:
|
|
351
|
+
o = _call(S._cg_call, cg, {"op": _op})
|
|
352
|
+
if "_raised" in (o if isinstance(o, dict) else {}):
|
|
353
|
+
raised.append((_op, o["_raised"]))
|
|
354
|
+
continue
|
|
355
|
+
check("M4-7 op=%-11s 返回 dict 时 action_source 恒定在场且取值合法" % _op,
|
|
356
|
+
isinstance(o, dict) and _g(o, "action_source") in
|
|
357
|
+
("explicit", "sig", "default", "none"),
|
|
358
|
+
str(o)[:110])
|
|
359
|
+
# 抛错面钉住(既有 fail-closed,非本闸范围):只允许 verify(缺 verdict)一支。
|
|
360
|
+
check("M4-8 抛错面只有既有的 verify 缺裁决一支(新增抛错即红)",
|
|
361
|
+
{r[0] for r in raised} <= {"verify"}, raised)
|
|
362
|
+
if raised:
|
|
363
|
+
print(" · 已知非 dict 返回:%s" % raised)
|
|
364
|
+
finally:
|
|
365
|
+
shutil.rmtree(root, ignore_errors=True)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
# ==========================================================================
|
|
369
|
+
# 定点变异自证(内存源码退回,不写盘)
|
|
370
|
+
# ==========================================================================
|
|
371
|
+
_SRC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "mcp_server.py")
|
|
372
|
+
|
|
373
|
+
_OLD_INT = ' v = a.get(key)\n return int(v) if v is not None else default'
|
|
374
|
+
_NEW_INT = ' v = a.get(key)\n return int(v) if v else default'
|
|
375
|
+
|
|
376
|
+
_OLD_NID = (' if a.get("node_id") is not None:\n'
|
|
377
|
+
' _nid = a["node_id"]\n'
|
|
378
|
+
' if not isinstance(_nid, str):\n'
|
|
379
|
+
' return {"ok": False, "error": "node_id_not_str",\n'
|
|
380
|
+
' "node_id": _nid, "got_type": type(_nid).__name__,\n'
|
|
381
|
+
' "hint": "node_id 必须是字符串 id;要按关键词检索请改用 "\n'
|
|
382
|
+
' "query(本层不再把类型错静默降级为检索)"}\n'
|
|
383
|
+
' return _node_view(cg.get(_nid), offset=int(a.get("offset") or 0))')
|
|
384
|
+
_NEW_NID = (' if a.get("node_id"):\n'
|
|
385
|
+
' return _node_view(cg.get(a["node_id"]),\n'
|
|
386
|
+
' offset=int(a.get("offset") or 0))')
|
|
387
|
+
|
|
388
|
+
_OLD_AS = ' out.setdefault("action_source", act_source)\n'
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _pre_fix_k_zero():
|
|
392
|
+
"""M1:把 `_int_arg` 退回真值判断(= 改动前 `int(a.get("k") or 20)`)。"""
|
|
393
|
+
return [(_OLD_INT, _NEW_INT)]
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _pre_fix_node_id_gate():
|
|
397
|
+
"""M2:把 read 分支的类型闸退回改动前两行(哈希可算者裸 null、不可哈希者 TypeError)。"""
|
|
398
|
+
return [(_OLD_NID, _NEW_NID)]
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _pre_fix_action_source():
|
|
402
|
+
"""M4:抽掉 action_source 的恒定挂载。"""
|
|
403
|
+
return [(_OLD_AS, "")]
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
_MUTATIONS = {
|
|
407
|
+
"k_zero": (_pre_fix_k_zero, ["M1-1", "M1-5", "M1-6", "M1-7", "M1-8", "M1-8b",
|
|
408
|
+
"M1-9"]),
|
|
409
|
+
"node_id_gate": (_pre_fix_node_id_gate, ["M2-3", "M2-4", "M2-5", "M2-7", "M2-8"]),
|
|
410
|
+
"action_source": (_pre_fix_action_source, ["M4-2", "M4-3", "M4-4", "M4-6", "M4-7"]),
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _mutated_surface(subs):
|
|
415
|
+
"""把 mcp_server.py 源码按 subs 替代一次 → exec 到**独立模块对象**(不落盘)。"""
|
|
416
|
+
src = open(_SRC_PATH, encoding="utf-8").read()
|
|
417
|
+
for old, new in subs:
|
|
418
|
+
if old not in src:
|
|
419
|
+
raise AssertionError("变异锚点未命中:%r" % old[:60])
|
|
420
|
+
src = src.replace(old, new, 1)
|
|
421
|
+
mod = types.ModuleType("mcp_server_mut")
|
|
422
|
+
mod.__file__ = _SRC_PATH
|
|
423
|
+
mod.__package__ = "md_cg"
|
|
424
|
+
exec(compile(src, _SRC_PATH, "exec"), mod.__dict__) # noqa: S102
|
|
425
|
+
return Surface(mod)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _run_all(S):
|
|
429
|
+
global PASS, FAILS
|
|
430
|
+
PASS, FAILS = 0, []
|
|
431
|
+
try:
|
|
432
|
+
group_m1(S)
|
|
433
|
+
except Exception as e: # noqa: BLE001
|
|
434
|
+
check("M1-EXCEPTION 分组中断 %s" % type(e).__name__, False, e)
|
|
435
|
+
try:
|
|
436
|
+
group_m2(S)
|
|
437
|
+
except Exception as e: # noqa: BLE001
|
|
438
|
+
check("M2-EXCEPTION 分组中断 %s" % type(e).__name__, False, e)
|
|
439
|
+
try:
|
|
440
|
+
group_m4(S)
|
|
441
|
+
except Exception as e: # noqa: BLE001
|
|
442
|
+
check("M4-EXCEPTION 分组中断 %s" % type(e).__name__, False, e)
|
|
443
|
+
return PASS, list(FAILS)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def main(argv):
|
|
447
|
+
which = None
|
|
448
|
+
for a in argv:
|
|
449
|
+
if a.startswith("--drop-fix"):
|
|
450
|
+
which = a.split("=", 1)[1] if "=" in a else "all"
|
|
451
|
+
if which is None:
|
|
452
|
+
ok, bad = _run_all(Surface(M))
|
|
453
|
+
print("\n==== 读面输入语义守卫:%d 通过%s ===="
|
|
454
|
+
% (ok, ",%d 失败:%s" % (len(bad), "; ".join(bad)) if bad else ""))
|
|
455
|
+
return 1 if bad else 0
|
|
456
|
+
names = list(_MUTATIONS) if which == "all" else [which]
|
|
457
|
+
rc = 0
|
|
458
|
+
print("\n=== 定点变异自证(抽掉修复 → 守卫必红 → 恢复 → 转绿)===")
|
|
459
|
+
for name in names:
|
|
460
|
+
if name not in _MUTATIONS:
|
|
461
|
+
print(" 未知变异 %r(可选:%s)" % (name, sorted(_MUTATIONS)))
|
|
462
|
+
return 2
|
|
463
|
+
fn, want = _MUTATIONS[name]
|
|
464
|
+
S = _mutated_surface(fn())
|
|
465
|
+
ok, bad = _run_all(S)
|
|
466
|
+
hit = [n for n in want
|
|
467
|
+
if any(b.split()[0] == n or b.startswith(n) for b in bad)]
|
|
468
|
+
missed = [n for n in want if n not in hit]
|
|
469
|
+
print(" [%s] 红项 %d 个:%s" % (name, len(bad), "; ".join(bad) or "(无)"))
|
|
470
|
+
if missed or not bad:
|
|
471
|
+
print(" FAIL 变异 %s 未打红预期断言:%s" % (name, missed or "全绿"))
|
|
472
|
+
rc = 1
|
|
473
|
+
else:
|
|
474
|
+
print(" ok 变异 %s 打红预期断言 %d 项" % (name, len(hit)))
|
|
475
|
+
ok2, bad2 = _run_all(Surface(M)) # 恢复 = 换回真模块
|
|
476
|
+
if bad2:
|
|
477
|
+
print(" FAIL 恢复后未转绿,残余红项:%s" % bad2)
|
|
478
|
+
rc = 1
|
|
479
|
+
else:
|
|
480
|
+
print(" ok 恢复后转绿(%d 通过,无残余)" % ok2)
|
|
481
|
+
return rc
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
if __name__ == "__main__":
|
|
485
|
+
try:
|
|
486
|
+
_rc = main(sys.argv[1:])
|
|
487
|
+
finally:
|
|
488
|
+
pass
|
|
489
|
+
sys.exit(_rc)
|