@furongjun1999/dsh-memory 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +588 -572
- 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/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/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_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/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_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/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 -3547
- package/md_cg/mdcos.py +4491 -4159
- 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/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_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 +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_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_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 +3 -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/zcode/AGENTS.md +10 -10
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""CH-1 守卫 · `scripts/rank_parity.py` 两侧词法打分口径接线。
|
|
3
|
+
|
|
4
|
+
来源:编排会话 2026-09-29 实测「Python search_rrf vs Rust mcdg-eval --serve」
|
|
5
|
+
逐位对拍三态,定位到**缺陷在 harness 的口径漏接线**而非产品侧:
|
|
6
|
+
|
|
7
|
+
态 逐位 集合 top-1 rc
|
|
8
|
+
缺省(修前) 7/10 10/10 7/10 1
|
|
9
|
+
MDCG_UNIFY_QUERY=0 6/10 9/10 6/10 1
|
|
10
|
+
Python 侧切 jaccard 10/10 10/10 10/10 0
|
|
11
|
+
|
|
12
|
+
两侧**产品缺省本就不同**(Python `md_cg/mdcg.py:SCORE_MODE` 缺省 `legacy`;
|
|
13
|
+
Rust `Cfg.jaccard` / `EngineConfig::default().jaccard` 缺省 `true`=jaccard),
|
|
14
|
+
而修前的 harness 两侧都不钉口径 ⇒ 长期拿两套词法公式互相比较,三条 DIFF 全是
|
|
15
|
+
「同一批分数分配给不同节点」的伪差异(词法排序在并列处劈叉)。
|
|
16
|
+
|
|
17
|
+
覆盖与判据(每条都对应修复契约的一项):
|
|
18
|
+
|
|
19
|
+
G1/G1b(契约①「两侧口径由单点显式钉住」)——临时库上跑 harness 的等价对拍,
|
|
20
|
+
断言**缺省态即逐位 10/10 · 集合 10/10 · top-1 10/10、退出码 0**:
|
|
21
|
+
G1 = 守卫自建临时库 + `--root`(库根由 tempfile.mkdtemp 造)
|
|
22
|
+
G1b = 完全缺省调用(harness 自建临时库,即编排侧实跑的那条命令)
|
|
23
|
+
同时断言头部行印出「两侧钉住」可观测面 `score=jaccard=rust jaccard`。
|
|
24
|
+
|
|
25
|
+
G2(契约②「口径漂移必须变响」)——只给 Python 侧设 `MDCG_SCORE_MODE=legacy`
|
|
26
|
+
(两侧口径被人为拨散):断言 harness **非 0 退出(3)**、打印**两侧实际
|
|
27
|
+
口径**与修复提示,且**不打印逐位读数**(未静默给出低分)。
|
|
28
|
+
|
|
29
|
+
G3(结构面·防回退)——源码锚点 + 行为自证:
|
|
30
|
+
单点常量 `SCORE_MODE = "jaccard"` 在位且 docstring 写明缺省口径;
|
|
31
|
+
Python 侧走**既有的** `eval_common.use_jaccard()`(且该单点自身仍是
|
|
32
|
+
`m.SCORE_MODE = "jaccard"`——**不新写第二套开关**);
|
|
33
|
+
Rust 侧 argv 显式 `--score` + `serve info` 回读面在位;
|
|
34
|
+
**两侧产品缺省未被本批改动**(负向锚点:Python 缺省仍 legacy、Rust 缺省
|
|
35
|
+
仍 jaccard——本批只显式化差异,不动检索口径);
|
|
36
|
+
`pin_python_score_mode()` 在缺省态把模块口径拨到 jaccard;env 声明冲突时
|
|
37
|
+
返回冲突说明且**不覆盖**模块口径。
|
|
38
|
+
|
|
39
|
+
G4(契约②的 fail-closed 面)——伪造一个「能应答但 info 不报 score」的过期
|
|
40
|
+
serve:断言 harness 退回 **rc=4** + 可执行的重编提示,且**不给逐位读数**
|
|
41
|
+
(读不到对方口径就不得继续对拍)。
|
|
42
|
+
|
|
43
|
+
运行:
|
|
44
|
+
python -X utf8 -m md_cg.test_rank_parity_score_mode # 正向
|
|
45
|
+
python -X utf8 -m md_cg.test_rank_parity_score_mode --mutate # 定点变异自证
|
|
46
|
+
python -X utf8 -m md_cg.test_rank_parity_score_mode --mutate --list
|
|
47
|
+
|
|
48
|
+
退出码(fail-closed):0 = 全绿;1 = 有断言失败 / 变异未按预期转红;
|
|
49
|
+
2 = **ANCHOR-MISS**(变异锚点在当前源码里找不到——实现改了却没同步本表,
|
|
50
|
+
本表就是判据,锚点漂移必须硬失败,不得静默跳过)。
|
|
51
|
+
|
|
52
|
+
依赖与「未构建」处置(**选 fail-closed**,不选 SKIP):本守卫需要 `rust/target/
|
|
53
|
+
release/mdcg-eval`(对拍的另一侧)。二进制缺失时 `main()` **前置判红**:打印可执行
|
|
54
|
+
的重编命令 `cd rust && cargo build --release` 并直接退出 **1**(不进断言组、不计
|
|
55
|
+
通过)——即「跑不起来」永远不等于「通过」。断言组内另留同款兜底(若二进制在组内
|
|
56
|
+
中途消失:依赖组逐条打印 `SKIP(未跑)` **不计入通过数**,同时补一条 fail-closed
|
|
57
|
+
红项),故任何口径下都不会出现「跳过=通过」。
|
|
58
|
+
|
|
59
|
+
**基线纪律**:本守卫**不以 git HEAD(或任何提交快照)为基线源**——触发红项的
|
|
60
|
+
「修前形态」全部由**在当前工作区源码上做定点文本变异**得到(`_MUTATIONS`),
|
|
61
|
+
基线随代码一起走、不随提交漂移。变异副本写入**本守卫的临时目录**(不进仓库
|
|
62
|
+
工作区),运行期经 PYTHONPATH 指向仓库根解析 `md_cg`,故 `_REPO` 派生物
|
|
63
|
+
(`--exe` 缺省、`MDCG_EN_ZH_MAP` 缺省)在本守卫里一律**显式传参**,两侧同源。
|
|
64
|
+
|
|
65
|
+
硬边界:不动任何在役数据根;库根一律 `tempfile.mkdtemp`;不 git add/commit/push;
|
|
66
|
+
不改 `data/policy.json`;不改任何检索算法与排序判据。
|
|
67
|
+
"""
|
|
68
|
+
from __future__ import annotations
|
|
69
|
+
|
|
70
|
+
import importlib.util
|
|
71
|
+
import os
|
|
72
|
+
import re
|
|
73
|
+
import shutil
|
|
74
|
+
import sys
|
|
75
|
+
import tempfile
|
|
76
|
+
import time
|
|
77
|
+
|
|
78
|
+
# ── 隔离前置:必须在 import md_cg 子模块**之前**(tokens/theory 等在模块级把
|
|
79
|
+
# aux_root() 冻成常量)——否则会指到真实 ~/.mdcg 并在那里建密钥/令牌库。
|
|
80
|
+
# 与 md_cg/test_opt_batch1_md_cg.py 同款前置(同一纪律)。 ──
|
|
81
|
+
_TMP = tempfile.mkdtemp(prefix="ch1_rp_")
|
|
82
|
+
os.environ["MDCG_AUX_ROOT"] = os.path.join(_TMP, "auxroot")
|
|
83
|
+
os.environ["MDCG_MASTER_KEY"] = "cd" * 32 # 哑主密钥(绝不触真实密钥面)
|
|
84
|
+
os.environ["MDCG_TOKEN_FILE"] = os.path.join(_TMP, "tokens.json")
|
|
85
|
+
os.environ["MDCG_ROOT"] = os.path.join(_TMP, "cgroot")
|
|
86
|
+
for _k in ("MDCG_TOKEN", "MDCG_TENANT", "MDCG_CLEARANCE", "MDCG_SESSION",
|
|
87
|
+
"DSH_SESSION_ID", "MDCG_LEGACY_ENV_AUTH", "MDCG_LEGACY_ENV_ADMIN",
|
|
88
|
+
"MDCG_CAN_ADMIN", "MDCG_CAN_WRITE", "MDCG_STATE_ROOT",
|
|
89
|
+
"MDCG_DATA_ROOT", "MDCG_TENANT_REGISTRY", "MDCG_VERIFIER_MODULES",
|
|
90
|
+
# 口径面:本守卫在**缺省态**上验证,故这两个开关必须干净
|
|
91
|
+
"MDCG_SCORE_MODE", "MDCG_UNIFY_QUERY"):
|
|
92
|
+
os.environ.pop(_k, None)
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
96
|
+
except AttributeError:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
100
|
+
_REPO = os.path.dirname(_HERE)
|
|
101
|
+
_SELF = os.path.abspath(__file__)
|
|
102
|
+
|
|
103
|
+
_HARNESS = os.path.join(_REPO, "scripts", "rank_parity.py")
|
|
104
|
+
_EXE = os.path.join(_REPO, "rust", "target", "release",
|
|
105
|
+
"mdcg-eval.exe" if os.name == "nt" else "mdcg-eval")
|
|
106
|
+
_EN_ZH_MAP = os.path.join(_REPO, "md_cg", "semantic", "en_zh_map.json")
|
|
107
|
+
|
|
108
|
+
# 被测 harness 的**可替换路径**:变异模式换成变异副本(临时目录),断言组一律
|
|
109
|
+
# 经本表取路径/取模块(不直接闭包捕获原路径),否则变异对断言不可见。
|
|
110
|
+
H = {"path": _HARNESS}
|
|
111
|
+
|
|
112
|
+
# 两侧**产品缺省**(负向锚点用:本批不得改动它们)
|
|
113
|
+
_PY_DEFAULT_ANCHOR = 'SCORE_MODE = os.environ.get("MDCG_SCORE_MODE") or "legacy"'
|
|
114
|
+
_RUST_CFG_DEFAULT_ANCHOR = "jaccard: true, // 缺省 jaccard"
|
|
115
|
+
|
|
116
|
+
_PASS: list = []
|
|
117
|
+
_FAIL: list = []
|
|
118
|
+
_SKIP: list = []
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def ok(cond, msg, extra=""):
|
|
122
|
+
(_PASS if cond else _FAIL).append(msg)
|
|
123
|
+
print((" PASS " if cond else " FAIL ") + msg
|
|
124
|
+
+ ((" ← " + str(extra)) if (extra and not cond) else ""))
|
|
125
|
+
return bool(cond)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def skip(msg):
|
|
129
|
+
_SKIP.append(msg)
|
|
130
|
+
print(" SKIP(未跑·不计通过) " + msg)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _read(path):
|
|
134
|
+
return open(path, encoding="utf-8").read()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _cleanup(path) -> bool:
|
|
138
|
+
import gc
|
|
139
|
+
for _ in range(6):
|
|
140
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
141
|
+
if not os.path.exists(path):
|
|
142
|
+
return True
|
|
143
|
+
gc.collect()
|
|
144
|
+
time.sleep(0.2)
|
|
145
|
+
return not os.path.exists(path)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# --------------------------------------------------------------- 被测面装载
|
|
149
|
+
_LOAD_CTR = [0]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _load_harness(path):
|
|
153
|
+
"""把 harness 源码装成独立模块(不执行 main;不落盘、不改工作区)。"""
|
|
154
|
+
_LOAD_CTR[0] += 1
|
|
155
|
+
name = "rank_parity_under_test_%d" % _LOAD_CTR[0]
|
|
156
|
+
spec = importlib.util.spec_from_file_location(name, path)
|
|
157
|
+
mod = importlib.util.module_from_spec(spec)
|
|
158
|
+
spec.loader.exec_module(mod)
|
|
159
|
+
return mod
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _run_harness(extra_args=(), env_overrides=None, exe=_EXE, timeout=900):
|
|
163
|
+
"""跑一次 harness(真进程:新解释器 = 无跨运行状态残留)。
|
|
164
|
+
|
|
165
|
+
显式传 `--exe` 与 `MDCG_EN_ZH_MAP`:变异副本位于临时目录,其 `_REPO` 派生
|
|
166
|
+
缺省不可信——两侧同源靠显式传参保证,否则「变异转红」可能来自路径错而非判据。
|
|
167
|
+
"""
|
|
168
|
+
env = dict(os.environ)
|
|
169
|
+
env["PYTHONUTF8"] = "1"
|
|
170
|
+
env["PYTHONPATH"] = _REPO + (
|
|
171
|
+
os.pathsep + env["PYTHONPATH"] if env.get("PYTHONPATH") else "")
|
|
172
|
+
env["MDCG_EN_ZH_MAP"] = _EN_ZH_MAP
|
|
173
|
+
env.update(env_overrides or {})
|
|
174
|
+
argv = [sys.executable, "-X", "utf8", H["path"], "--exe", exe] + list(extra_args)
|
|
175
|
+
return subprocess_run(argv, env)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def subprocess_run(argv, env):
|
|
179
|
+
import subprocess
|
|
180
|
+
return subprocess.run(argv, capture_output=True, text=True, encoding="utf-8",
|
|
181
|
+
errors="replace", env=env, cwd=_REPO, timeout=900)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _summary(out):
|
|
185
|
+
"""从 harness 输出取 (逐位, 集合, top-1, 总数);无该行返回 None。"""
|
|
186
|
+
m = re.search(r"逐位一致 (\d+)/(\d+) · 集合一致 (\d+)/(\d+) · "
|
|
187
|
+
r"top-1 一致 (\d+)/(\d+)", out or "")
|
|
188
|
+
if not m:
|
|
189
|
+
return None
|
|
190
|
+
g = [int(x) for x in m.groups()]
|
|
191
|
+
return (g[0], g[2], g[4], g[1])
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _fake_stale_exe(d):
|
|
195
|
+
"""伪造「能应答但 info 不报 score」的过期 serve(二进制过期形态)。"""
|
|
196
|
+
py = os.path.join(d, "fake_stale_serve.py")
|
|
197
|
+
with open(py, "w", encoding="utf-8") as f:
|
|
198
|
+
f.write(
|
|
199
|
+
"import sys, json\n"
|
|
200
|
+
"for line in sys.stdin:\n"
|
|
201
|
+
" line = line.strip()\n"
|
|
202
|
+
" if not line:\n"
|
|
203
|
+
" continue\n"
|
|
204
|
+
" if line == 'quit':\n"
|
|
205
|
+
" break\n"
|
|
206
|
+
" req = json.loads(line)\n"
|
|
207
|
+
" if req.get('op') == 'info':\n"
|
|
208
|
+
# 关键:**不报 score**(修前 serve 的 info 面)
|
|
209
|
+
" resp = {'ok': True, 'root': 'stale', 'docs': 0}\n"
|
|
210
|
+
" else:\n"
|
|
211
|
+
" resp = {'ok': True, 'hits': []}\n"
|
|
212
|
+
" sys.stdout.write(json.dumps(resp) + '\\n')\n"
|
|
213
|
+
" sys.stdout.flush()\n")
|
|
214
|
+
if os.name == "nt":
|
|
215
|
+
launcher = os.path.join(d, "fake_stale_serve.cmd")
|
|
216
|
+
with open(launcher, "w", encoding="utf-8") as f:
|
|
217
|
+
f.write('@echo off\r\n"%s" "%s" %%*\r\n' % (sys.executable, py))
|
|
218
|
+
else:
|
|
219
|
+
launcher = os.path.join(d, "fake_stale_serve.sh")
|
|
220
|
+
with open(launcher, "w", encoding="utf-8") as f:
|
|
221
|
+
f.write('#!/bin/sh\nexec "%s" "%s" "$@"\n' % (sys.executable, py))
|
|
222
|
+
os.chmod(launcher, 0o755)
|
|
223
|
+
return launcher
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# ================================================================ G0 前置自证
|
|
227
|
+
def g0():
|
|
228
|
+
print("== G0 隔离与前置自证 ==")
|
|
229
|
+
ok(os.path.abspath(os.environ["MDCG_AUX_ROOT"]).lower().startswith(_TMP.lower()),
|
|
230
|
+
"G0a 辅助根落在守卫临时目录(绝不触真实凭据/密钥面)",
|
|
231
|
+
os.environ["MDCG_AUX_ROOT"])
|
|
232
|
+
ok("MDCG_SCORE_MODE" not in os.environ and "MDCG_UNIFY_QUERY" not in os.environ,
|
|
233
|
+
"G0b 口径类 env 干净:本守卫确实在**缺省态**上验证",
|
|
234
|
+
os.environ.get("MDCG_SCORE_MODE"))
|
|
235
|
+
# 基线源自证(反「基线绑提交」):变异加载器直接 open 当前工作区源文件,
|
|
236
|
+
# 读到的就是它此刻在盘上的内容——不含任何提交快照读取面。
|
|
237
|
+
live = _read(_HARNESS)
|
|
238
|
+
mut_path, src = _write_mutated(live, 'SCORE_MODE = "jaccard"', 'SCORE_MODE = "jaccard" # MUT')
|
|
239
|
+
ok(mut_path is not None and src == live,
|
|
240
|
+
"G0c 变异基线源 = 当前工作区源码(非 git HEAD/提交快照)", mut_path)
|
|
241
|
+
print(" 被测面:%s" % H["path"])
|
|
242
|
+
print(" 对拍另一侧:%s" % _EXE)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# ================================================================ G1 缺省态对拍
|
|
246
|
+
def g1():
|
|
247
|
+
print("== G1 契约①:缺省态即 10/10/10(守卫自建临时库 + --root)==")
|
|
248
|
+
if not os.path.isfile(_EXE):
|
|
249
|
+
skip("G1 对拍(rust 二进制缺失)—— 修复:cd rust && cargo build --release")
|
|
250
|
+
ok(False, "G1 前置:rust release 二进制在位(fail-closed:跑不起来≠通过)", _EXE)
|
|
251
|
+
return
|
|
252
|
+
mod = _load_harness(H["path"])
|
|
253
|
+
root = tempfile.mkdtemp(prefix="ch1_db_") # 实验库根:系统临时目录
|
|
254
|
+
try:
|
|
255
|
+
n = mod.build_cogmap(root)
|
|
256
|
+
ok(n == 30, "G1a 临时库建成 30 节点(正对照:夹具不空转)", n)
|
|
257
|
+
p = _run_harness(["--root", root])
|
|
258
|
+
s = _summary(p.stdout)
|
|
259
|
+
ok(p.returncode == 0, "G1b 缺省态退出码 0(口径已接线:两侧同钉 jaccard)",
|
|
260
|
+
"rc=%s out=%s" % (p.returncode, p.stdout.strip()[-200:]))
|
|
261
|
+
ok(s is not None and s[0] == s[3] == 10,
|
|
262
|
+
"G1c 逐位一致 10/10(修前缺省态 7/10)", s)
|
|
263
|
+
ok(s is not None and s[1] == 10, "G1d 集合一致 10/10", s)
|
|
264
|
+
ok(s is not None and s[2] == 10, "G1e top-1 一致 10/10(修前 7/10)", s)
|
|
265
|
+
ok("[DIFF]" not in p.stdout, "G1f 无 DIFF 行(三条伪差异已消失)",
|
|
266
|
+
[l for l in p.stdout.splitlines() if "DIFF" in l][:3])
|
|
267
|
+
ok(re.search(r"score=jaccard=rust jaccard", p.stdout) is not None,
|
|
268
|
+
"G1g 头部行印出两侧实际口径(自描述:score=jaccard=rust jaccard)",
|
|
269
|
+
p.stdout.strip().splitlines()[0] if p.stdout.strip() else "")
|
|
270
|
+
finally:
|
|
271
|
+
_cleanup(root)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
# ================================================================ G1b 缺省调用
|
|
275
|
+
def g1b():
|
|
276
|
+
print("== G1b 契约①:完全缺省调用(harness 自建临时库,编排侧实跑形态)==")
|
|
277
|
+
if not os.path.isfile(_EXE):
|
|
278
|
+
skip("G1b 对拍(rust 二进制缺失)—— 修复:cd rust && cargo build --release")
|
|
279
|
+
ok(False, "G1b 前置:rust release 二进制在位(fail-closed)", _EXE)
|
|
280
|
+
return
|
|
281
|
+
p = _run_harness([])
|
|
282
|
+
s = _summary(p.stdout)
|
|
283
|
+
ok(p.returncode == 0, "G1b-1 缺省调用退出码 0",
|
|
284
|
+
"rc=%s out=%s" % (p.returncode, p.stdout.strip()[-200:]))
|
|
285
|
+
ok(s == (10, 10, 10, 10), "G1b-2 缺省调用逐位/集合/top-1 = 10/10/10", s)
|
|
286
|
+
ok("[DIFF]" not in p.stdout, "G1b-3 无 DIFF 行")
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
# ================================================================ G2 漂移变响
|
|
290
|
+
def g2():
|
|
291
|
+
print("== G2 契约②:两侧口径被人为拨散 → 非 0 退出并报口径不一致 ==")
|
|
292
|
+
if not os.path.isfile(_EXE):
|
|
293
|
+
skip("G2 漂移闸(rust 二进制缺失)—— 修复:cd rust && cargo build --release")
|
|
294
|
+
ok(False, "G2 前置:rust release 二进制在位(fail-closed)", _EXE)
|
|
295
|
+
return
|
|
296
|
+
p = _run_harness([], env_overrides={"MDCG_SCORE_MODE": "legacy"})
|
|
297
|
+
out = p.stdout
|
|
298
|
+
ok(p.returncode != 0, "G2a 非 0 退出(口径不一致不得继续对拍)", "rc=%s" % p.returncode)
|
|
299
|
+
ok(p.returncode == 3, "G2b 退出码 = 3(口径不一致专用码,与逐位劈叉 1 区分)",
|
|
300
|
+
"rc=%s" % p.returncode)
|
|
301
|
+
ok("口径不一致" in out, "G2c 打印口径不一致判定")
|
|
302
|
+
ok(re.search(r"python 侧实际口径 = legacy", out) is not None,
|
|
303
|
+
"G2d 打印 Python 侧**实际**口径 = legacy(读回值,非假设)",
|
|
304
|
+
out.strip()[:200])
|
|
305
|
+
ok(re.search(r"rust\s+侧实际口径 = jaccard", out) is not None,
|
|
306
|
+
"G2e 打印 Rust 侧**实际**口径 = jaccard(回读自被拉起进程)",
|
|
307
|
+
out.strip()[:200])
|
|
308
|
+
ok("修复" in out, "G2f 打印修复提示")
|
|
309
|
+
ok(_summary(out) is None,
|
|
310
|
+
"G2g **不打印逐位读数**(未静默给出低分——修前形态即此处静默)",
|
|
311
|
+
_summary(out))
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# ================================================================ G3 结构面
|
|
315
|
+
def g3():
|
|
316
|
+
print("== G3 结构面:口径钉住的单点与回读面在位、两侧产品缺省未被改动 ==")
|
|
317
|
+
src = _read(H["path"])
|
|
318
|
+
ok('SCORE_MODE = "jaccard"' in src,
|
|
319
|
+
'G3a harness 口径**唯一单点**常量 SCORE_MODE = "jaccard" 在位')
|
|
320
|
+
ok("缺省口径 = jaccard" in src,
|
|
321
|
+
"G3b 顶部用法注释写明本 harness 的缺省口径 = jaccard(与 eval 面同判据)")
|
|
322
|
+
# ② Python 侧走既有单点,且该单点自身未被改写成第二套开关
|
|
323
|
+
ok(any(l.strip() == "eval_common.use_jaccard()" for l in src.splitlines()),
|
|
324
|
+
"G3c Python 侧经**既有** md_cg/eval_common.py::use_jaccard() 注入(非自造开关)")
|
|
325
|
+
ec = _read(os.path.join(_HERE, "eval_common.py"))
|
|
326
|
+
ok('m.SCORE_MODE = "jaccard"' in ec,
|
|
327
|
+
"G3d 评测面单点 eval_common.use_jaccard 本体仍是 m.SCORE_MODE = \"jaccard\"")
|
|
328
|
+
# ③ Rust 侧显式入参 + 回读面
|
|
329
|
+
ok(re.search(r'"--score",\s*score', src) is not None,
|
|
330
|
+
'G3e Rust 侧 argv 显式钉住口径("--score", score),不吃 Config::default()')
|
|
331
|
+
ok("rust_reported_score" in src and "score" in src,
|
|
332
|
+
"G3f harness 回读 Rust 侧自报口径(rust_reported_score / info.score)")
|
|
333
|
+
serve = _read(os.path.join(_REPO, "rust", "src", "serve.rs"))
|
|
334
|
+
ok('"score".to_string()' in serve,
|
|
335
|
+
"G3g rust/src/serve.rs 的 info 面回带 score(回读面的服务端一侧)")
|
|
336
|
+
eng = _read(os.path.join(_REPO, "rust", "src", "engine.rs"))
|
|
337
|
+
ok("pub fn jaccard(&self) -> bool" in eng,
|
|
338
|
+
"G3h engine 暴露实际生效口径的访问器(回读面取值处)")
|
|
339
|
+
main_rs = _read(os.path.join(_REPO, "rust", "src", "main.rs"))
|
|
340
|
+
ok("mdcg_eval::engine::score_label" in main_rs,
|
|
341
|
+
"G3i 口径标签映射单点(main.rs 转调 engine::score_label,与 serve 同源)")
|
|
342
|
+
# 负向锚点:两侧**产品缺省**不得被本批改动(只显式化差异,不动检索口径)
|
|
343
|
+
md = _read(os.path.join(_HERE, "mdcg.py"))
|
|
344
|
+
ok(_PY_DEFAULT_ANCHOR in md,
|
|
345
|
+
"G3j 负向锚点:Python 产品缺省仍是 legacy(md_cg/mdcg.py:SCORE_MODE 未被本批改)")
|
|
346
|
+
ok(_RUST_CFG_DEFAULT_ANCHOR in main_rs,
|
|
347
|
+
"G3k 负向锚点:Rust Cfg 缺省仍是 jaccard: true(Config::default() 未被本批改)")
|
|
348
|
+
# 行为自证:缺省(legacy)态下钉住体把模块口径拨到 jaccard,并**读回**该值
|
|
349
|
+
import md_cg.mdcg as m
|
|
350
|
+
before = m.SCORE_MODE
|
|
351
|
+
ok(before == "legacy",
|
|
352
|
+
"G3l 前置:本进程内 mdcg.SCORE_MODE 为产品缺省 legacy(缺省态验证的前提)", before)
|
|
353
|
+
mod = _load_harness(H["path"])
|
|
354
|
+
got, conflict = mod.pin_python_score_mode()
|
|
355
|
+
ok(got == "jaccard" and conflict is None,
|
|
356
|
+
"G3m 行为自证:缺省态下 pin 后读回口径 = jaccard(且无冲突)",
|
|
357
|
+
"got=%s conflict=%s" % (got, conflict))
|
|
358
|
+
ok(m.SCORE_MODE == "jaccard",
|
|
359
|
+
"G3n 行为自证:模块级 SCORE_MODE 实际被拨到 jaccard(mdcos 读同一份)",
|
|
360
|
+
m.SCORE_MODE)
|
|
361
|
+
# env 声明冲突:报冲突且**不覆盖**(不静默无视操作者声明)
|
|
362
|
+
m.SCORE_MODE = "legacy"
|
|
363
|
+
os.environ["MDCG_SCORE_MODE"] = "legacy"
|
|
364
|
+
try:
|
|
365
|
+
got2, conflict2 = mod.pin_python_score_mode()
|
|
366
|
+
ok(conflict2 is not None and got2 == "legacy",
|
|
367
|
+
"G3o env 声明冲突时返回冲突说明且实际口径仍为 legacy(不静默覆盖)",
|
|
368
|
+
"got=%s conflict=%s" % (got2, conflict2))
|
|
369
|
+
finally:
|
|
370
|
+
os.environ.pop("MDCG_SCORE_MODE", None)
|
|
371
|
+
m.SCORE_MODE = before
|
|
372
|
+
# A/B 路径同口径(同一 harness 只有一套口径单点)
|
|
373
|
+
ok("口径 {py_mode}" in src,
|
|
374
|
+
"G3p --ab 路径头部自描述口径(不再隐式吃缺省)")
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
# ================================================================ G4 过期二进制
|
|
378
|
+
def g4():
|
|
379
|
+
print("== G4 fail-closed:Rust 侧不报口径(二进制过期)→ 不得继续对拍 ==")
|
|
380
|
+
d = tempfile.mkdtemp(prefix="ch1_stale_")
|
|
381
|
+
try:
|
|
382
|
+
launcher = _fake_stale_exe(d)
|
|
383
|
+
p = _run_harness([], exe=launcher)
|
|
384
|
+
out = p.stdout
|
|
385
|
+
ok(p.returncode == 4, "G4a 退出码 = 4(口径不可读专用码)",
|
|
386
|
+
"rc=%s out=%s" % (p.returncode, out.strip()[-300:]))
|
|
387
|
+
ok("口径不可读" in out, "G4b 打印「口径不可读」判定")
|
|
388
|
+
ok("cargo build --release" in out, "G4c 给出可执行的重编提示")
|
|
389
|
+
ok(_summary(out) is None, "G4d 不给逐位读数(读不到对方口径不得继续对拍)",
|
|
390
|
+
_summary(out))
|
|
391
|
+
finally:
|
|
392
|
+
_cleanup(d)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
# ================================================================ 变异表
|
|
396
|
+
# 每条 = (名称, 源码锚点, 替换文本, 预期转红条数)。
|
|
397
|
+
# 锚点必须逐字命中当前源码(找不到 → ANCHOR-MISS → 退出码 2,fail-closed)。
|
|
398
|
+
_MUTATIONS = (
|
|
399
|
+
("CH-1 去掉 Python 侧口径钉住(撤 eval_common.use_jaccard 调用)",
|
|
400
|
+
" eval_common.use_jaccard()\n",
|
|
401
|
+
" pass # MUT:Python 侧回退吃产品缺省 legacy\n",
|
|
402
|
+
10),
|
|
403
|
+
("CH-1 撤掉两侧口径对照闸(漂移不再变响)",
|
|
404
|
+
" if py_conflict or py_mode != rs_mode:\n",
|
|
405
|
+
" if False: # MUT:漂移闸拆除\n",
|
|
406
|
+
6),
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
class _Name:
|
|
411
|
+
"""变异副本的稳定命名容器(与 _MUT_CTR 配合)。"""
|
|
412
|
+
n = 0
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _write_mutated(src: str, old: str, new: str):
|
|
416
|
+
"""把 `src` 做定点文本替换后写入**守卫临时目录**(不进仓库工作区)。
|
|
417
|
+
|
|
418
|
+
返回 (变异副本路径 | None, 源文本)。None = 锚点未命中(调用方报 ANCHOR-MISS)。
|
|
419
|
+
"""
|
|
420
|
+
if old not in src:
|
|
421
|
+
return None, src
|
|
422
|
+
_Name.n += 1
|
|
423
|
+
path = os.path.join(_TMP, "mut_rank_parity_%d.py" % _Name.n)
|
|
424
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
425
|
+
f.write(src.replace(old, new, 1))
|
|
426
|
+
return path, src
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _run_groups() -> int:
|
|
430
|
+
"""跑全部断言组,返回失败数(变异核验复用)。"""
|
|
431
|
+
_PASS.clear()
|
|
432
|
+
_FAIL.clear()
|
|
433
|
+
_SKIP.clear()
|
|
434
|
+
for g in (g0, g1, g1b, g2, g3, g4):
|
|
435
|
+
try:
|
|
436
|
+
g()
|
|
437
|
+
except Exception as exc: # noqa: BLE001
|
|
438
|
+
ok(False, "断言组 %s 抛异常:%s" % (g.__name__, exc))
|
|
439
|
+
return len(_FAIL)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _mutate_mode(list_only: bool = False) -> int:
|
|
443
|
+
print("!! 定点变异自证:逐条把修复点改回缺陷形态,套件必须按预期条数转红\n")
|
|
444
|
+
if list_only:
|
|
445
|
+
for name, old, new, exp in _MUTATIONS:
|
|
446
|
+
print(" %-52s expect_red=%d" % (name, exp))
|
|
447
|
+
return 0
|
|
448
|
+
|
|
449
|
+
import contextlib
|
|
450
|
+
import io
|
|
451
|
+
anchor_miss = []
|
|
452
|
+
bad = []
|
|
453
|
+
with contextlib.redirect_stdout(io.StringIO()):
|
|
454
|
+
clean = _run_groups()
|
|
455
|
+
print(" 未变异基线:失败=%d(必须为 0)" % clean)
|
|
456
|
+
if clean:
|
|
457
|
+
bad.append("未变异基线即失败")
|
|
458
|
+
|
|
459
|
+
live_src = _read(_HARNESS)
|
|
460
|
+
for name, old, new, expect in _MUTATIONS:
|
|
461
|
+
path, _ = _write_mutated(live_src, old, new)
|
|
462
|
+
if path is None:
|
|
463
|
+
print(" ANCHOR-MISS %s —— 锚点在 scripts/rank_parity.py 当前源码里"
|
|
464
|
+
"找不到(实现改了却没同步本表;基线不得静默漂移)" % name)
|
|
465
|
+
anchor_miss.append(name)
|
|
466
|
+
continue
|
|
467
|
+
live_path = H["path"]
|
|
468
|
+
H["path"] = path
|
|
469
|
+
try:
|
|
470
|
+
with contextlib.redirect_stdout(buf := io.StringIO()):
|
|
471
|
+
fails_extra = _run_groups()
|
|
472
|
+
detail = buf.getvalue()
|
|
473
|
+
finally:
|
|
474
|
+
H["path"] = live_path
|
|
475
|
+
red = [l for l in detail.splitlines() if l.strip().startswith("FAIL ")]
|
|
476
|
+
verdict = "红" if fails_extra else "**仍全绿 = 该判据空转**"
|
|
477
|
+
mark = "OK " if fails_extra == expect else "MISMATCH"
|
|
478
|
+
print(" %s %-52s 红项=%d 预期=%d %s"
|
|
479
|
+
% (mark, name, fails_extra, expect, verdict))
|
|
480
|
+
for l in red:
|
|
481
|
+
print(" " + l.strip()[5:])
|
|
482
|
+
if fails_extra != expect:
|
|
483
|
+
bad.append("%s(红=%d 预期=%d)" % (name, fails_extra, expect))
|
|
484
|
+
|
|
485
|
+
if anchor_miss:
|
|
486
|
+
print("\nANCHOR-MISS:%s" % "、".join(anchor_miss))
|
|
487
|
+
print("退出码 2(fail-closed):变异表锚点漂移即判失败,不得静默跳过")
|
|
488
|
+
return 2
|
|
489
|
+
print("\n定点变异自证:%s" % ("PASS(每处判据都被打红且恰好命中预期项数)"
|
|
490
|
+
if not bad else "FAIL —— " + "、".join(bad)))
|
|
491
|
+
return 0 if not bad else 1
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def main() -> int:
|
|
495
|
+
if not os.path.isfile(_EXE):
|
|
496
|
+
print("!! rust release 二进制缺失:%s" % _EXE)
|
|
497
|
+
print(" 修复:cd rust && cargo build --release")
|
|
498
|
+
print(" 处置:**fail-closed**(本守卫不计通过、直接退出 1;"
|
|
499
|
+
"不采用静默 SKIP 而返回 0——跑不起来永远不等于通过)")
|
|
500
|
+
return 1
|
|
501
|
+
if "--mutate" in sys.argv:
|
|
502
|
+
try:
|
|
503
|
+
return _mutate_mode("--list" in sys.argv)
|
|
504
|
+
finally:
|
|
505
|
+
_cleanup(_TMP)
|
|
506
|
+
try:
|
|
507
|
+
n_fail = _run_groups()
|
|
508
|
+
print("\nCH-1 守卫(rank_parity 两侧口径接线):%d 通过,%d 失败,%d 跳过"
|
|
509
|
+
% (len(_PASS), n_fail, len(_SKIP)))
|
|
510
|
+
return 0 if not n_fail else 1
|
|
511
|
+
finally:
|
|
512
|
+
_cleanup(_TMP)
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
if __name__ == "__main__":
|
|
516
|
+
sys.exit(main())
|