@furongjun1999/dsh-memory 0.6.1 → 0.7.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 +49 -18
- package/docs/README.md +1 -0
- package/docs/eval/cons200_/345/206/262/347/252/201/346/243/200/346/265/213/351/200/211/351/235/242_/345/256/236/346/226/275/350/256/260/345/275/225_v1.0.md +223 -0
- package/docs/eval/cons200_/345/206/262/347/252/201/346/243/200/346/265/213/351/200/211/351/235/242_/345/256/236/346/226/275/350/256/260/345/275/225_v1.1.md +340 -0
- package/docs/eval/issue50_/345/205/203/346/225/260/346/215/256/351/200/217/344/274/240/344/270/216/345/205/234/345/272/225_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +437 -0
- package/docs/eval/issue50_/345/215/212/351/207/215/345/244/215/345/276/205/345/256/232/345/244/215/346/240/270_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +488 -0
- package/docs/eval/issue50_/345/276/205/345/256/232/345/244/215/346/240/270/345/205/245/351/230/237_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +428 -0
- package/docs/eval/issue50_/350/257/273/351/235/242/344/277/235/346/212/244/345/217/252/350/256/244/346/230/276/345/274/217/346/235/245/346/272/220_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +271 -0
- package/docs/eval/issue50_/351/207/215/350/246/201/345/272/246/345/220/214/346/272/220/344/270/216/344/277/235/346/212/244/350/257/255/344/271/211_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +306 -0
- package/docs/eval/issue51_/344/270/200/351/224/256/345/256/211/350/243/205/345/244/261/350/264/245_/345/275/222/345/261/236/345/210/244/345/256/232_v1.0.md +58 -0
- package/docs/eval/issue52_/346/235/241/344/273/266/345/205/210/350/241/214/344/270/216/346/210/252/346/226/255/345/217/257/350/247/202/346/265/213_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +360 -0
- package/docs/eval//344/270/211/346/241/243/350/207/252/346/262/273_/346/255/245/351/252/244/342/221/241/346/241/243/344/275/215/345/215/225/344/270/200/345/205/245/345/217/243_/350/220/275/347/240/201/350/256/260/345/275/225_v1.0.md +651 -0
- package/docs/eval//344/270/211/346/241/243/350/207/252/346/262/273_/346/255/245/351/252/244/342/221/242/345/217/230/346/233/264/345/215/225/344/270/216/345/233/236/346/273/232/345/216/237/350/257/255_/350/220/275/347/240/201/350/256/260/345/275/225_v1.0.md +439 -0
- package/docs/eval//344/270/211/346/241/243/350/207/252/346/262/273_/346/255/245/351/252/244/342/221/243/345/207/206/345/205/245/350/257/273/346/225/260_/350/220/275/347/240/201/350/256/260/345/275/225_v1.0.md +1337 -0
- package/docs/eval//344/270/211/346/241/243/350/207/252/346/262/273_/346/255/245/351/252/244/342/221/244/346/224/266/345/256/230/344/270/216/345/205/250/351/223/276/351/252/214/346/224/266_/350/220/275/347/240/201/350/256/260/345/275/225_v1.0.md +1817 -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/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/217/221/345/270/20309_/346/243/200/347/264/242/351/235/242/344/270/211/346/211/271/346/224/266/345/217/243_v1.0.md +66 -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//347/254/2543/345/261/202stg/347/273/223/346/236/204/347/264/242/345/274/225_/345/256/236/346/226/275/350/256/260/345/275/225_v1.0.md +250 -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 +32 -2
- package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +88 -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 +47 -11
- 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/plans/stg/346/235/241/344/273/266/345/214/226/344/270/216/347/273/223/346/236/204/347/264/242/345/274/225_/350/256/276/350/256/241_v0.1.md +124 -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/256/260/345/277/206/350/207/252/345/244/204/347/220/206/344/270/211/346/241/243/350/207/252/346/262/273_/350/256/276/350/256/241_v0.2.md +207 -0
- package/md_cg/admission.py +718 -0
- package/md_cg/autonomy_modes.py +642 -0
- package/md_cg/bench_e2e_locomo_qa.py +11 -4
- package/md_cg/chain.py +47 -0
- package/md_cg/consistency.py +133 -8
- package/md_cg/forgetting.py +38 -6
- package/md_cg/freshness.py +527 -0
- package/md_cg/generation.py +409 -0
- package/md_cg/hotcache.py +4 -1
- package/md_cg/lifecycle.py +30 -2
- package/md_cg/mcp_server.py +191 -26
- package/md_cg/mdcg.py +594 -39
- package/md_cg/mdcos.py +869 -38
- package/md_cg/nodefile.py +74 -1
- package/md_cg/protect.py +79 -4
- package/md_cg/provenance.py +1 -1
- package/md_cg/review_cli.py +31 -2
- package/md_cg/rollback.py +411 -0
- package/md_cg/rollback_cli.py +104 -0
- 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/stg.py +400 -50
- package/md_cg/stgidx.py +281 -0
- package/md_cg/sustain.py +146 -9
- package/md_cg/test_auto_defaults.py +424 -0
- package/md_cg/test_autonomy_admission.py +1098 -0
- package/md_cg/test_autonomy_modes.py +1972 -0
- package/md_cg/test_b1_auto_id_multiproc.py +7 -0
- package/md_cg/test_b1b2_write_face.py +7 -0
- package/md_cg/test_b3_merge_keeps_content.py +7 -0
- package/md_cg/test_boundary_hit.py +410 -0
- package/md_cg/test_cons200_scan_selection.py +791 -0
- package/md_cg/test_en_pipeline.py +22 -13
- package/md_cg/test_generation_guard.py +352 -0
- package/md_cg/test_h4_sustain_snapshot.py +14 -4
- package/md_cg/test_i50a_half_dup_defer.py +408 -0
- package/md_cg/test_i50b_defer_to_review_queue.py +545 -0
- package/md_cg/test_i50c_meta_passthrough.py +608 -0
- package/md_cg/test_i50d_importance_source.py +531 -0
- package/md_cg/test_i50e_readside_protection.py +656 -0
- package/md_cg/test_issue39_utf8_stdio.py +9 -1
- package/md_cg/test_issue52_scan_condition_first.py +677 -0
- package/md_cg/test_linkref.py +7 -0
- package/md_cg/test_mode_parity.py +1393 -0
- package/md_cg/test_mutation_rollback.py +805 -0
- package/md_cg/test_n204_n205_n226_n227_n228_n229_exit_gates.py +1 -1
- package/md_cg/test_n212_n213_n224_generation_gates.py +14 -8
- package/md_cg/test_n214_n215_n221_n222_write_face_gates.py +7 -0
- package/md_cg/test_n230_dirty_replay.py +375 -0
- package/md_cg/test_p2_mcp.py +8 -0
- package/md_cg/test_p2_six_elements.py +463 -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_p9_forget_protect.py +7 -0
- package/md_cg/test_p9c_dedup_hints.py +7 -0
- package/md_cg/test_policy_required_ccg.py +5 -1
- package/md_cg/test_protocol.py +7 -0
- package/md_cg/test_rank_parity_score_mode.py +6 -0
- package/md_cg/test_semantic_canonical.py +5 -3
- package/md_cg/test_sleep.py +611 -0
- package/md_cg/test_sleep_p1.py +784 -0
- package/md_cg/test_stgidx_index_parity.py +1020 -0
- package/md_cg/test_time_core_lint.py +968 -0
- package/md_cg/test_unify_default_off.py +701 -0
- package/md_cg/test_unify_scope.py +182 -0
- package/md_cg/test_writelimit.py +36 -9
- package/md_cg/test_writepipe.py +5 -2
- package/md_cg/weights.py +15 -1
- package/md_cg/whitebox_kb/aeis_core/time_core.py +8 -0
- package/md_cg/writepipe.py +101 -2
- package/package.json +3 -2
- package/skills/plugin.json +1 -1
- package/utf8_boot.py +237 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""`auto_*` 四档缺省「单一真源」守卫(P0-2,2026-10-01)。
|
|
3
|
+
|
|
4
|
+
契约(设计稿 `docs/plans/睡眠与自迭代_功能优化设计_v0.4.md` §八 P0-2 / §4.7):
|
|
5
|
+
|
|
6
|
+
同一 `(root, name)` 的两条入口必须得到**同一组** `auto_*` 取值;
|
|
7
|
+
缺省只许有**一份定义**(`sustain.AUTO_DEFAULTS` + 三个读取器),
|
|
8
|
+
两个调用点不得再写第二处字面量。
|
|
9
|
+
|
|
10
|
+
改动前实测(探针读数,逐条引 `path:line`):
|
|
11
|
+
|
|
12
|
+
op 路径(工具面 `sustain action=start`) `auto_tidy=bool(a.get("auto_tidy", False))`
|
|
13
|
+
→ **False**(mcp_server.py:1639)
|
|
14
|
+
env 路径(常驻 serve 自启 `_start_sustain`)`auto_tidy=os.environ.get("MDCG_AUTO_TIDY","1") not in (...)`
|
|
15
|
+
→ **True**(mcp_server.py:3595-3596)
|
|
16
|
+
另三项 auto_heal/auto_scrub/auto_evolve 两入口一致(True / False / False);
|
|
17
|
+
`SustainLoop.__init__` 形参另有一份 `auto_tidy=False`(sustain.py:966)——
|
|
18
|
+
即「单一真源尚未成立,全仓 auto_tidy 缺省字面量至少两处」。
|
|
19
|
+
|
|
20
|
+
**本轮对外可见的缺省变更(裁决值 = 开)**:`auto_tidy` 缺省由 False 收敛为
|
|
21
|
+
**True**(取 env 路径既有值);opt-out = `MDCG_AUTO_TIDY=0`(env 路径)/
|
|
22
|
+
显式 `auto_tidy=false`(op 路径)。另三项**逐项未变**(G4 钉死,防「顺手改多了」)。
|
|
23
|
+
|
|
24
|
+
断言分组:
|
|
25
|
+
|
|
26
|
+
G0 隔离与基线自证——库根/状态根/辅助根/心跳目录全落守卫临时目录(绝不碰在役
|
|
27
|
+
库与 `~/.mdcg`);断言真源表与三个读取器在位。
|
|
28
|
+
G1 真源表与读取器——四项值、键集、env 键映射、关断字面量固定;`auto_default`
|
|
29
|
+
逐项一致;`auto_from_env` 的缺省字面量**由表推出**(真值→"1"、假值→"0",
|
|
30
|
+
与改动前两处字面量同义)。
|
|
31
|
+
G2 三面同源读数(端到端,真起循环)——env 路径(`_start_sustain`)、op 路径
|
|
32
|
+
(`_sustain_call` 的 start 分支)、构造面(`SustainLoop(cg)`)三面在同一档上
|
|
33
|
+
取值相同且**都等于真源表**;并验两侧 opt-out / 显式入参仍优先。
|
|
34
|
+
G3 单一真源(静态)——带引号字面量 `"MDCG_AUTO_TIDY"` 在生产源码里恰 1 处
|
|
35
|
+
(在 `AUTO_ENVS`);`mcp_server.py` 内 `a.get("auto_*"` /
|
|
36
|
+
`os.environ.get("MDCG_AUTO_*"` / `os.environ.get("MDCG_SUSTAIN_AUTOHEAL"` 零命中;
|
|
37
|
+
两入口各恰好一处读取器调用。(`md_cg/test_*.py` 不在判据面内:本守卫自己的
|
|
38
|
+
变异表就含这些字面量,属红基线工具面。)
|
|
39
|
+
G4 对外可见变更的披露面——`auto_tidy` 缺省为 True 且 op 路径无参即得 True;
|
|
40
|
+
另三项缺省与本轮前逐项相同;opt-out 名取自 `AUTO_ENVS` 而非硬编码。
|
|
41
|
+
|
|
42
|
+
运行:python -X utf8 -m md_cg.test_auto_defaults
|
|
43
|
+
python -X utf8 -m md_cg.test_auto_defaults --mutate # 定点变异自证
|
|
44
|
+
python -X utf8 -m md_cg.test_auto_defaults --mutate --list # 只列变异表
|
|
45
|
+
|
|
46
|
+
退出码(fail-closed):0 = 全绿;1 = 有断言失败 / 变异未按预期转红;
|
|
47
|
+
2 = ANCHOR-MISS(变异锚点在当前源码里找不到——实现改了却没同步本表,
|
|
48
|
+
锚点漂移必须硬失败,不得静默跳过)。
|
|
49
|
+
|
|
50
|
+
**基线纪律**:本守卫**不以 git HEAD 为基线源**——「改动前形态」由在当前工作区
|
|
51
|
+
源码上做**定点文本变异**(`_MUTATIONS`)复现,锚点漂移即退出码 2。
|
|
52
|
+
"""
|
|
53
|
+
from __future__ import annotations
|
|
54
|
+
|
|
55
|
+
import contextlib
|
|
56
|
+
import io
|
|
57
|
+
import os
|
|
58
|
+
import shutil
|
|
59
|
+
import sys
|
|
60
|
+
import tempfile
|
|
61
|
+
import types
|
|
62
|
+
|
|
63
|
+
from . import mcp_server as MCP
|
|
64
|
+
from . import sustain
|
|
65
|
+
|
|
66
|
+
_PASS = []
|
|
67
|
+
_FAIL = []
|
|
68
|
+
_SKIP = []
|
|
69
|
+
#: 变异模式下供静态组读取的**源码文本**(rel → text);未变异时读盘。
|
|
70
|
+
_SRC = {}
|
|
71
|
+
|
|
72
|
+
_REAL_SUSTAIN = sustain
|
|
73
|
+
_REAL_MCP = MCP
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def ok(cond, msg, extra=""):
|
|
77
|
+
(_PASS if cond else _FAIL).append(msg)
|
|
78
|
+
print((" PASS " if cond else " FAIL ") + msg
|
|
79
|
+
+ ((" ← " + str(extra)) if (extra and not cond) else ""))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def skip(msg):
|
|
83
|
+
_SKIP.append(msg)
|
|
84
|
+
print(" SKIP " + msg)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _repo_root() -> str:
|
|
88
|
+
return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _rel_text(rel: str) -> str:
|
|
92
|
+
"""源码文本:变异模式下取变异后的文本,否则读盘。"""
|
|
93
|
+
if rel in _SRC:
|
|
94
|
+
return _SRC[rel]
|
|
95
|
+
with open(os.path.join(_repo_root(), rel), encoding="utf-8") as f:
|
|
96
|
+
return f.read()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _md_cg_py_texts():
|
|
100
|
+
d = os.path.join(_repo_root(), "md_cg")
|
|
101
|
+
return {"md_cg/" + fn: _rel_text("md_cg/" + fn)
|
|
102
|
+
for fn in sorted(os.listdir(d)) if fn.endswith(".py")}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------------- 沙箱
|
|
106
|
+
_TMP = tempfile.mkdtemp(prefix="mdcg_autodef_")
|
|
107
|
+
_SAVED = {}
|
|
108
|
+
_GEN = [0]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _sandbox_env():
|
|
112
|
+
"""把四条根全钉进守卫临时目录;原值留待还原(绝不碰在役库 / ~/.mdcg)。"""
|
|
113
|
+
for k in ("MDCG_ROOT", "MDCG_AUX_ROOT", "MDCG_STATE_ROOT", "MDCG_SUSTAIN_DIR",
|
|
114
|
+
"MDCG_SUBSTAIN", "MDCG_SUSTAIN", "MDCG_SUSTAIN_NAME",
|
|
115
|
+
"MDCG_AUTO_TIDY", "MDCG_AUTO_SCRUB", "MDCG_AUTO_EVOLVE",
|
|
116
|
+
"MDCG_SUSTAIN_AUTOHEAL"):
|
|
117
|
+
_SAVED[k] = os.environ.get(k)
|
|
118
|
+
os.environ["MDCG_ROOT"] = os.path.join(_TMP, "cg")
|
|
119
|
+
os.environ["MDCG_AUX_ROOT"] = os.path.join(_TMP, "aux")
|
|
120
|
+
os.environ["MDCG_STATE_ROOT"] = os.path.join(_TMP, "state")
|
|
121
|
+
os.environ["MDCG_SUSTAIN_DIR"] = os.path.join(_TMP, "sustain")
|
|
122
|
+
os.environ["MDCG_SUBSTAIN"] = "0"
|
|
123
|
+
os.environ.pop("MDCG_SUSTAIN", None)
|
|
124
|
+
for k in ("MDCG_AUTO_TIDY", "MDCG_AUTO_SCRUB", "MDCG_AUTO_EVOLVE",
|
|
125
|
+
"MDCG_SUSTAIN_AUTOHEAL"):
|
|
126
|
+
os.environ.pop(k, None)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _restore_env():
|
|
130
|
+
for k, v in _SAVED.items():
|
|
131
|
+
if v is None:
|
|
132
|
+
os.environ.pop(k, None)
|
|
133
|
+
else:
|
|
134
|
+
os.environ[k] = v
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _cg(name="cg"):
|
|
138
|
+
"""每轮 `_run_groups` 用**全新根**:`sustain.ensure_loop` 按 (root,name) 复用
|
|
139
|
+
实例,同一根跨轮复用会让变异轮读到上一轮的循环(假绿)。"""
|
|
140
|
+
from .mdcos import MdCGOS
|
|
141
|
+
root = os.path.join(_TMP, "gen%d" % _GEN[0], name)
|
|
142
|
+
os.makedirs(root, exist_ok=True)
|
|
143
|
+
return MdCGOS(root)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ---------------------------------------------------------------- G0
|
|
147
|
+
def g0():
|
|
148
|
+
print("== G0 隔离与基线自证 ==")
|
|
149
|
+
ok(hasattr(_REAL_SUSTAIN, "AUTO_DEFAULTS"),
|
|
150
|
+
"G0a sustain.AUTO_DEFAULTS 存在(缺省真源表)")
|
|
151
|
+
ok(all(hasattr(_REAL_SUSTAIN, n)
|
|
152
|
+
for n in ("auto_default", "auto_from_env", "auto_from_args")),
|
|
153
|
+
"G0b 三个读取器(auto_default / auto_from_env / auto_from_args)均在位")
|
|
154
|
+
ok(os.environ["MDCG_ROOT"].startswith(_TMP)
|
|
155
|
+
and os.environ["MDCG_AUX_ROOT"].startswith(_TMP),
|
|
156
|
+
"G0c 沙箱:库根与辅助根都在守卫临时目录内", os.environ["MDCG_ROOT"])
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# ---------------------------------------------------------------- G1
|
|
160
|
+
_EXPECT = {"auto_heal": True, "auto_scrub": False,
|
|
161
|
+
"auto_evolve": False, "auto_tidy": True}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def g1():
|
|
165
|
+
print("== G1 真源表与读取器 ==")
|
|
166
|
+
S = globals()["sustain"]
|
|
167
|
+
ok(dict(S.AUTO_DEFAULTS) == _EXPECT,
|
|
168
|
+
"G1 真源表四项与裁决值一致(含 auto_tidy=True——本轮唯一的对外可见变更)",
|
|
169
|
+
S.AUTO_DEFAULTS)
|
|
170
|
+
ok(set(S.AUTO_ENVS) == set(S.AUTO_DEFAULTS),
|
|
171
|
+
"G1a env 键映射与真源表同键集", sorted(S.AUTO_ENVS))
|
|
172
|
+
ok(S.AUTO_ENVS["auto_tidy"] == "MDCG_AUTO_TIDY",
|
|
173
|
+
"G1b auto_tidy 的 env 键名 = MDCG_AUTO_TIDY(opt-out 名)")
|
|
174
|
+
ok(tuple(S.AUTO_OFF_VALUES) == ("0", "false", "False"),
|
|
175
|
+
"G1c 关断字面量与改动前两入口逐字一致(0 / false / False)",
|
|
176
|
+
S.AUTO_OFF_VALUES)
|
|
177
|
+
for k, v in _EXPECT.items():
|
|
178
|
+
ok(S.auto_default(k) is v,
|
|
179
|
+
"G1d auto_default(%s) 与表一致 = %r" % (k, v))
|
|
180
|
+
for k, v in _EXPECT.items():
|
|
181
|
+
ok(S.auto_from_env(k, {}) is v,
|
|
182
|
+
"G1e auto_from_env(%s) 空环境下回落真源缺省 %r(默认字面量由表推出)"
|
|
183
|
+
% (k, v))
|
|
184
|
+
for k in _EXPECT:
|
|
185
|
+
for off in ("0", "false", "False", "FALSE ", ""):
|
|
186
|
+
got = S.auto_from_env(k, {S.AUTO_ENVS[k]: off})
|
|
187
|
+
want = off not in S.AUTO_OFF_VALUES
|
|
188
|
+
ok(got is want,
|
|
189
|
+
"G1f auto_from_env(%s) 取 %r → %r(关断口径不变)" % (k, off, want),
|
|
190
|
+
got)
|
|
191
|
+
ok(S.auto_from_env("auto_tidy", {"MDCG_AUTO_TIDY": "1"}) is True,
|
|
192
|
+
"G1g 显式 1 仍为开(开态仍可达)")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# ---------------------------------------------------------------- G2
|
|
196
|
+
def g2():
|
|
197
|
+
print("== G2 三面同源读数(端到端,真起循环)==")
|
|
198
|
+
S = globals()["sustain"]
|
|
199
|
+
M = globals()["MCP"]
|
|
200
|
+
cg = _cg("cg_g2")
|
|
201
|
+
os.environ.pop("MDCG_AUTO_TIDY", None)
|
|
202
|
+
os.environ["MDCG_SUSTAIN_NAME"] = "env_edge"
|
|
203
|
+
lp = M._start_sustain(cg)
|
|
204
|
+
ok(lp is not None, "G2 env 路径确已起循环(前置)")
|
|
205
|
+
ok(lp.auto_tidy is True,
|
|
206
|
+
"G2a env 路径(_start_sustain)无 env 覆盖时 auto_tidy=True(生产路径既有值)",
|
|
207
|
+
lp.auto_tidy)
|
|
208
|
+
os.environ["MDCG_AUTO_TIDY"] = "0"
|
|
209
|
+
os.environ["MDCG_SUSTAIN_NAME"] = "env_off"
|
|
210
|
+
lp2 = M._start_sustain(cg)
|
|
211
|
+
ok(lp2 is not None and lp2.auto_tidy is False,
|
|
212
|
+
"G2b env 路径 opt-out:MDCG_AUTO_TIDY=0 → auto_tidy=False",
|
|
213
|
+
(lp2.auto_tidy if lp2 else None))
|
|
214
|
+
os.environ.pop("MDCG_AUTO_TIDY", None)
|
|
215
|
+
ti = {"beat_interval": 3600, "heal_interval": 3600, "scrub_interval": 3600,
|
|
216
|
+
"evolve_interval": 3600, "tidy_interval": 3600}
|
|
217
|
+
r = M._sustain_call(cg, dict({"action": "start", "name": "op_default"}, **ti))
|
|
218
|
+
opl = S.get_loop(cg, "op_default")
|
|
219
|
+
ok("loop" in r and opl is not None, "G2c op 路径确已起循环(前置)", r)
|
|
220
|
+
ok(opl is not None and opl.auto_tidy is True,
|
|
221
|
+
"G2d op 路径(sustain action=start)不传参时 auto_tidy=True——**本轮的缺省变更**",
|
|
222
|
+
(opl.auto_tidy if opl else None))
|
|
223
|
+
M._sustain_call(cg, dict({"action": "start", "name": "op_off",
|
|
224
|
+
"auto_tidy": False}, **ti))
|
|
225
|
+
opl2 = S.get_loop(cg, "op_off")
|
|
226
|
+
ok(opl2 is not None and opl2.auto_tidy is False,
|
|
227
|
+
"G2e op 路径显式 auto_tidy=false → False(显式入参优先于真源表)",
|
|
228
|
+
(opl2.auto_tidy if opl2 else None))
|
|
229
|
+
M._sustain_call(cg, dict({"action": "start", "name": "op_face"}, **ti))
|
|
230
|
+
env_face = S.get_loop(cg, "env_edge")
|
|
231
|
+
op_face = S.get_loop(cg, "op_face")
|
|
232
|
+
ctor_face = S.SustainLoop(cg, name="ctor_face",
|
|
233
|
+
d=os.environ["MDCG_SUSTAIN_DIR"])
|
|
234
|
+
ok(env_face is not None and op_face is not None,
|
|
235
|
+
"G2f 前置:env / op 两面循环都在册")
|
|
236
|
+
for k in _EXPECT:
|
|
237
|
+
trio = (getattr(env_face, k), getattr(op_face, k), getattr(ctor_face, k))
|
|
238
|
+
ok(trio[0] == trio[1] == trio[2] == _EXPECT[k],
|
|
239
|
+
"G2g 三面同值 %s:env/op/构造 = %r,真源 = %r" % (k, trio, _EXPECT[k]))
|
|
240
|
+
S.stop_all()
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# ---------------------------------------------------------------- G3
|
|
244
|
+
def g3():
|
|
245
|
+
print("== G3 单一真源(静态)==")
|
|
246
|
+
texts = _md_cg_py_texts()
|
|
247
|
+
prod = {rel: t for rel, t in texts.items()
|
|
248
|
+
if not os.path.basename(rel).startswith("test_")}
|
|
249
|
+
quoted = sorted((rel, t.count('"MDCG_AUTO_TIDY"')) for rel, t in prod.items()
|
|
250
|
+
if t.count('"MDCG_AUTO_TIDY"'))
|
|
251
|
+
ok(quoted == [("md_cg/sustain.py", 1)],
|
|
252
|
+
"G3 带引号字面量 \"MDCG_AUTO_TIDY\" 在生产源码里恰 1 处(在 AUTO_ENVS)",
|
|
253
|
+
quoted)
|
|
254
|
+
pats = ('os.environ.get("MDCG_AUTO_TIDY"', 'a.get("auto_tidy"',
|
|
255
|
+
'a.get("auto_heal"', 'a.get("auto_scrub"', 'a.get("auto_evolve"',
|
|
256
|
+
'os.environ.get("MDCG_AUTO_SCRUB"',
|
|
257
|
+
'os.environ.get("MDCG_AUTO_EVOLVE"',
|
|
258
|
+
'os.environ.get("MDCG_SUSTAIN_AUTOHEAL"')
|
|
259
|
+
bad = [(rel, p, t.count(p)) for rel, t in prod.items()
|
|
260
|
+
for p in pats if t.count(p)]
|
|
261
|
+
ok(not bad, "G3a 生产源码里零命中「缺省字面量」形态(^ 见 detail)", bad)
|
|
262
|
+
s = _rel_text("md_cg/sustain.py")
|
|
263
|
+
ok("AUTO_DEFAULTS = {" in s, "G3b 真源表在 sustain.py 定义")
|
|
264
|
+
ok(s.count("auto_tidy: bool = AUTO_DEFAULTS[") == 1,
|
|
265
|
+
"G3c SustainLoop 形参默认值引用真源表(不写第二处字面量)")
|
|
266
|
+
m = _rel_text("md_cg/mcp_server.py")
|
|
267
|
+
ok(m.count('auto_tidy=sustain.auto_from_args("auto_tidy", a)') == 1,
|
|
268
|
+
"G3d op 路径恰一处经 auto_from_args 读 auto_tidy")
|
|
269
|
+
ok(m.count('auto_tidy=sustain.auto_from_env("auto_tidy")') == 1,
|
|
270
|
+
"G3e env 路径恰一处经 auto_from_env 读 auto_tidy")
|
|
271
|
+
ok('"auto_tidy", False' not in m and 'auto_tidy=False)' not in m,
|
|
272
|
+
"G3f mcp_server.py 内不再有 auto_tidy 的 False 缺省字面量")
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# ---------------------------------------------------------------- G4
|
|
276
|
+
def g4():
|
|
277
|
+
print("== G4 变更披露面(防顺手改多了)==")
|
|
278
|
+
S = globals()["sustain"]
|
|
279
|
+
ok(S.AUTO_DEFAULTS["auto_tidy"] is True,
|
|
280
|
+
"G4 裁决值 = 开:auto_tidy 真源缺省为 True")
|
|
281
|
+
ok(S.AUTO_DEFAULTS["auto_heal"] is True
|
|
282
|
+
and S.AUTO_DEFAULTS["auto_scrub"] is False
|
|
283
|
+
and S.AUTO_DEFAULTS["auto_evolve"] is False,
|
|
284
|
+
"G4a 另三项缺省与本轮前逐项相同(heal=True / scrub=False / evolve=False)",
|
|
285
|
+
S.AUTO_DEFAULTS)
|
|
286
|
+
ok(S.AUTO_ENVS["auto_tidy"] == "MDCG_AUTO_TIDY",
|
|
287
|
+
"G4b opt-out env 名(自真源读)= MDCG_AUTO_TIDY")
|
|
288
|
+
ok(S.auto_from_args("auto_tidy", {"auto_tidy": None}) is False,
|
|
289
|
+
"G4c 显式传 None(键在)→ False,与改动前 bool(a.get(name, default)) 同义")
|
|
290
|
+
ok(S.auto_from_args("auto_tidy", {"other": 1}) is True,
|
|
291
|
+
"G4d 缺键才回落真源缺省(True)")
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
_GROUPS = (g0, g1, g2, g3, g4)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
# ---------------------------------------------------------------- 变异表
|
|
298
|
+
# 锚点 = (名字, 模式, rel, old, new, 预期红项数下限)。
|
|
299
|
+
# "mod" = 改**可执行实现**(exec 成模块并换进 `sys.modules`,供 `from . import` 面读到)
|
|
300
|
+
# "src" = 只改**源码文本**(静态组 G3 读 `_SRC`,不 exec)
|
|
301
|
+
# 「改动前形态」的复现即在这里:
|
|
302
|
+
# M1 把真源表里的 auto_tidy 改回 False(= 旧的 op 路径字面量)
|
|
303
|
+
# M2 让 op 路径读取器不回落真源(= 旧的「另一处字面量」)
|
|
304
|
+
# M3 把 env 路径改回旧的手写 env 读取(= 旧 env 路径形态 + 第二处字面量)
|
|
305
|
+
_MUTATIONS = (
|
|
306
|
+
("真源表 auto_tidy 改回 False(旧 op 路径值)", "mod", "md_cg/sustain.py",
|
|
307
|
+
'"auto_evolve": False, "auto_tidy": True}',
|
|
308
|
+
'"auto_evolve": False, "auto_tidy": False}', 1),
|
|
309
|
+
("op 路径读取器不回落真源(旧「另一处字面量」)", "mod", "md_cg/sustain.py",
|
|
310
|
+
" return bool(args[name])\n return auto_default(name)",
|
|
311
|
+
" return bool(args[name])\n return False", 1),
|
|
312
|
+
("env 路径改回手写 env 读取(第二处 MDCG_AUTO_TIDY 字面量)", "src",
|
|
313
|
+
"md_cg/mcp_server.py",
|
|
314
|
+
'auto_tidy=sustain.auto_from_env("auto_tidy"))',
|
|
315
|
+
'auto_tidy=os.environ.get("MDCG_AUTO_TIDY", "1") not in ("0", "false", "False"))',
|
|
316
|
+
1),
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _run_groups() -> int:
|
|
321
|
+
_GEN[0] += 1
|
|
322
|
+
_PASS.clear()
|
|
323
|
+
_FAIL.clear()
|
|
324
|
+
for g in _GROUPS:
|
|
325
|
+
try:
|
|
326
|
+
g()
|
|
327
|
+
except Exception as exc: # noqa: BLE001
|
|
328
|
+
ok(False, "断言组 %s 抛异常:%s" % (g.__name__, exc))
|
|
329
|
+
import traceback
|
|
330
|
+
print(traceback.format_exc()[-800:])
|
|
331
|
+
for mod in (globals().get("sustain"), _REAL_SUSTAIN):
|
|
332
|
+
try:
|
|
333
|
+
mod.stop_all()
|
|
334
|
+
except Exception: # noqa: BLE001
|
|
335
|
+
pass
|
|
336
|
+
return len(_FAIL)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _exec_module(name: str, rel: str, text: str):
|
|
340
|
+
ns = {"__name__": name, "__package__": "md_cg",
|
|
341
|
+
"__file__": os.path.join(_repo_root(), rel)}
|
|
342
|
+
exec(compile(text, rel, "exec"), ns) # noqa: S102 —— 基线自证用
|
|
343
|
+
m = types.ModuleType(name)
|
|
344
|
+
m.__dict__.update(ns)
|
|
345
|
+
return m
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _mutate_mode(list_only: bool = False) -> int:
|
|
349
|
+
print("!! 定点变异自证:逐条把「单一真源」改回改动前形态,套件必须转红\n")
|
|
350
|
+
if list_only:
|
|
351
|
+
for name, kind, rel, _o, _n, exp in _MUTATIONS:
|
|
352
|
+
print(" %-46s [%s] expect_red>=%d (%s)" % (name, kind, exp, rel))
|
|
353
|
+
return 0
|
|
354
|
+
anchor_miss, bad = [], []
|
|
355
|
+
with contextlib.redirect_stdout(io.StringIO()):
|
|
356
|
+
clean = _run_groups()
|
|
357
|
+
print(" 未变异基线:失败=%d(必须为 0)" % clean)
|
|
358
|
+
if clean:
|
|
359
|
+
bad.append("未变异基线即失败")
|
|
360
|
+
|
|
361
|
+
for name, kind, rel, old, new, expect in _MUTATIONS:
|
|
362
|
+
src = _rel_text(rel)
|
|
363
|
+
if old not in src:
|
|
364
|
+
print(" ANCHOR-MISS %s —— 锚点在 %s 源码里找不到(实现改了却没同步"
|
|
365
|
+
"本表;锚点漂移不得静默)" % (name, rel))
|
|
366
|
+
anchor_miss.append(name)
|
|
367
|
+
continue
|
|
368
|
+
mut_src = src.replace(old, new, 1)
|
|
369
|
+
_SRC[rel] = mut_src
|
|
370
|
+
live = None
|
|
371
|
+
try:
|
|
372
|
+
if kind == "mod":
|
|
373
|
+
live = sys.modules["md_cg.sustain"]
|
|
374
|
+
mut = _exec_module("md_cg._autodef_mut", rel, mut_src)
|
|
375
|
+
sys.modules["md_cg.sustain"] = mut
|
|
376
|
+
globals()["sustain"] = mut
|
|
377
|
+
with contextlib.redirect_stdout(buf := io.StringIO()):
|
|
378
|
+
reds = _run_groups()
|
|
379
|
+
detail = buf.getvalue()
|
|
380
|
+
finally:
|
|
381
|
+
if live is not None:
|
|
382
|
+
sys.modules["md_cg.sustain"] = live
|
|
383
|
+
globals()["sustain"] = _REAL_SUSTAIN
|
|
384
|
+
_SRC.pop(rel, None)
|
|
385
|
+
red_lines = [l for l in detail.splitlines()
|
|
386
|
+
if l.strip().startswith("FAIL ")]
|
|
387
|
+
verdict = "红" if reds else "**仍全绿 = 该判据空转**"
|
|
388
|
+
mark = "OK " if reds >= expect else "MISMATCH"
|
|
389
|
+
print(" %s %-46s 红项=%d 预期>=%d %s"
|
|
390
|
+
% (mark, name, reds, expect, verdict))
|
|
391
|
+
for l in red_lines[:6]:
|
|
392
|
+
print(" " + l.strip()[5:])
|
|
393
|
+
if reds < expect:
|
|
394
|
+
bad.append("%s(红=%d 预期>=%d)" % (name, reds, expect))
|
|
395
|
+
|
|
396
|
+
if anchor_miss:
|
|
397
|
+
print("\nANCHOR-MISS:%s" % "、".join(anchor_miss))
|
|
398
|
+
print("退出码 2(fail-closed):变异表锚点漂移即判失败,不得静默跳过")
|
|
399
|
+
return 2
|
|
400
|
+
print("\n定点变异自证:%s"
|
|
401
|
+
% ("PASS(每处判据都被打红)" if not bad else "FAIL —— " + "、".join(bad)))
|
|
402
|
+
return 0 if not bad else 1
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def main() -> int:
|
|
406
|
+
_sandbox_env()
|
|
407
|
+
try:
|
|
408
|
+
if "--mutate" in sys.argv:
|
|
409
|
+
return _mutate_mode("--list" in sys.argv)
|
|
410
|
+
n = _run_groups()
|
|
411
|
+
print("\nauto_* 缺省单一真源守卫:%d 通过,%d 失败,%d 跳过"
|
|
412
|
+
% (len(_PASS), n, len(_SKIP)))
|
|
413
|
+
return 0 if not n else 1
|
|
414
|
+
finally:
|
|
415
|
+
try:
|
|
416
|
+
_REAL_SUSTAIN.stop_all()
|
|
417
|
+
except Exception: # noqa: BLE001
|
|
418
|
+
pass
|
|
419
|
+
_restore_env()
|
|
420
|
+
shutil.rmtree(_TMP, ignore_errors=True)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
if __name__ == "__main__":
|
|
424
|
+
sys.exit(main())
|