@furongjun1999/dsh-memory 0.7.1 → 0.7.3
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 +58 -48
- package/codebuddy/CODEBUDDY.md +9 -9
- package/docs/README.md +148 -144
- package/docs/discipline/harnesses.yaml +16 -8
- package/docs/discipline/templates/zcode-user.md.tmpl +49 -0
- package/docs/eval/AGI/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_/345/205/255/345/256/266/350/256/260/345/277/206/347/263/273/347/273/237/345/220/214/345/260/272/345/256/236/346/265/213_v3.1.md +340 -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 +2 -2
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +2 -2
- 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 +5 -5
- 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 +9 -9
- 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 +4 -4
- 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 +1 -1
- 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 +1 -1
- 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 +3 -3
- 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 +3 -3
- 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 +7 -7
- 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 +2 -2
- 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 +1 -1
- 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 +7 -7
- 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 +10 -10
- 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 +4 -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 +1 -1
- 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 +1 -1
- package/docs/eval//345/217/221/345/270/20310_/350/257/273/351/235/242/346/215/237/345/235/217UTF8/345/256/266/346/227/217/346/224/266/345/217/243_v1.0.md +71 -0
- package/docs/eval//345/217/221/345/270/20311_/345/244/226/346/212/245/345/205/255/350/277/236/344/277/256/344/270/216/344/270/226/347/225/214/346/250/241/345/236/213/345/272/225/345/272/247_v1.0.md +91 -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 +2 -2
- package/docs/eval//347/224/250/346/210/267/350/243/201/345/206/263/345/244/204/347/275/256_/347/254/254/344/270/200/346/211/271_v1.0.md +195 -0
- package/docs/eval//347/224/250/346/210/267/350/243/201/345/206/263/345/244/204/347/275/256_/347/254/254/344/272/214/346/211/271B6_/346/234/254/346/234/272/350/267/257/345/276/204/347/233/270/345/257/271/345/214/226_v1.0.md +78 -0
- package/docs/eval//347/253/257/345/210/260/347/253/257LoCoMoQA/345/220/214/345/217/243/345/276/204/345/257/271/347/205/247_v1.0.md +1 -1
- package/docs/eval//347/253/257/345/210/260/347/253/257/345/271/262/346/211/260/346/261/240/350/257/204/346/265/213_/347/241/256/345/256/232/346/200/247/350/243/201/345/206/263vsLLM_judge_v1.1.md +3 -3
- 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 +1 -1
- 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 +1 -1
- 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_v26.md +196 -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_v27.md +259 -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_v28.md +217 -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_v29.md +270 -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_v30.md +231 -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_v31.md +281 -0
- package/docs/hive//345/256/211/345/205/250/345/256/241/350/256/241/345/256/236/351/224/232_v0.1.md +1 -1
- 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/hive//350/234/202/345/267/242/345/217/214/345/256/236/344/276/213/344/272/222/351/252/214_/350/256/276/350/256/241/345/256/232/347/250/277.md +1 -1
- package/docs/hive//350/234/202/345/267/242/350/256/276/350/256/241_/347/220/206/350/256/272/345/257/271/351/275/220_v0.1.md +17 -17
- package/docs/hive//350/234/202/345/267/242/350/277/255/344/273/243_/345/256/217/350/247/202/344/270/216/347/276/244/344/275/223/350/260/203/345/272/246_v0.1.md +1 -1
- package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +2 -2
- 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 +42 -41
- package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +29 -8
- 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 +71 -1
- 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 +1 -1
- package/docs/plans//345/244/232/344/270/273/344/275/223/344/270/226/347/225/214/346/250/241/345/236/213_/345/257/271/351/275/220/350/257/204/344/274/260/344/270/216/350/220/275/345/234/260/350/256/276/350/256/241_v0.1.md +141 -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 +2 -2
- package/docs/plans//350/257/255/344/271/211/346/227/266/347/251/272/345/233/276/350/241/245/345/205/250_/344/270/226/347/225/214/346/250/241/345/236/213/345/212/237/350/203/275/347/253/257_/350/256/276/350/256/241_v0.1.md +107 -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 +9 -8
- package/docs//347/247/244_/350/256/260/345/277/206/347/263/273/347/273/237/350/257/204/346/265/213/350/247/204/350/214/203_v2.1.md +225 -0
- package/dsh/cordis-patch-profile-web.example.yml +2 -2
- package/lib/bridge.js +9 -0
- package/lib/hooks.d.ts +8 -1
- package/lib/hooks.js +94 -32
- package/lib/lib/hook_audit.d.ts +55 -0
- package/lib/lib/hook_audit.js +208 -0
- package/lib/lib/mutual.js +5 -1
- package/lib/lib/roleplay_web.js +17 -2
- package/md_cg/auditview.py +248 -0
- package/md_cg/bench6_arms.py +10 -6
- package/md_cg/bench_e2e_judge.py +4 -2
- package/md_cg/branches.py +29 -3
- package/md_cg/census.py +2 -1
- package/md_cg/consistency.py +38 -13
- package/md_cg/datapath.py +38 -11
- package/md_cg/forgetting.py +15 -2
- package/md_cg/freshness.py +32 -11
- package/md_cg/ghostref.py +130 -0
- package/md_cg/judgment_manifest.py +7 -0
- package/md_cg/mcp_server.py +58 -1
- package/md_cg/mdcg.py +4923 -4764
- package/md_cg/mdcos.py +40 -15
- package/md_cg/mreview/locate.py +1 -1
- package/md_cg/nodefile.py +77 -13
- package/md_cg/readcache.py +29 -4
- package/md_cg/rollback.py +12 -3
- package/md_cg/routing.py +10 -1
- package/md_cg/selfreport.py +12 -3
- package/md_cg/sleep.py +129 -13
- package/md_cg/sources.py +1 -1
- package/md_cg/state_events.py +131 -0
- package/md_cg/sustain.py +1436 -1436
- package/md_cg/test_auditview.py +277 -0
- package/md_cg/test_autonomy_modes.py +2 -1
- package/md_cg/test_boot_openblas_guard.py +197 -0
- package/md_cg/test_ccg_form_parity.py +95 -4
- package/md_cg/test_cons200_scan_selection.py +4 -2
- package/md_cg/test_corrupt_utf8_read_surfaces.py +451 -0
- package/md_cg/test_datapath_device_name.py +62 -3
- package/md_cg/test_generation_guard.py +20 -11
- package/md_cg/test_ghostref.py +182 -0
- package/md_cg/test_health_corrupt_utf8.py +37 -0
- package/md_cg/test_index_crossprocess_reload.py +39 -24
- package/md_cg/test_interop.py +1 -1
- package/md_cg/test_issue52_scan_condition_first.py +1 -1
- package/md_cg/test_issue53_cipher_health_hash.py +385 -0
- package/md_cg/test_lock.py +16 -7
- package/md_cg/test_m3_h9_semantic_guard.py +1 -1
- package/md_cg/test_n230_dirty_replay.py +375 -375
- package/md_cg/test_n238_body_text_parity.py +156 -0
- package/md_cg/test_n276_probe_hoist.py +199 -0
- package/md_cg/test_p4_freshness.py +95 -1
- package/md_cg/test_policy_required_ccg.py +3 -1
- package/md_cg/test_readcache_prodpath.py +66 -0
- package/md_cg/test_sleep.py +58 -1
- package/md_cg/test_sleep_p1.py +218 -2
- package/md_cg/test_state_events.py +178 -0
- package/md_cg/test_time_core_lint.py +8 -1
- package/md_cg/test_writepipe.py +8 -5
- package/md_cg/tokens.py +11 -2
- package/md_cg/writepipe.py +76 -0
- package/package.json +2 -2
- package/skills/plugin.json +1 -1
- package/src/bridge.ts +9 -0
- package/src/hooks.ts +576 -517
- package/src/lib/hook_audit.ts +243 -0
- package/src/lib/mutual.ts +5 -1
- package/src/lib/roleplay_web.ts +17 -2
- package/zcode/AGENTS.md +9 -9
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""守卫:坏 UTF-8 节点在五个读取面的行为契约(PR #54 面 + 三处读取面同口径扩展)
|
|
3
|
+
|
|
4
|
+
背景(取证自 `git show --stat ccef3bc0 / 30e0580b` 与本文件注释逐字引用的现场)
|
|
5
|
+
--------------------------------------------------------------------------
|
|
6
|
+
`30e0580b`(健康检查)与 `ccef3bc0`(读取面)落了「损坏非 UTF-8 节点不应让读取
|
|
7
|
+
面崩溃」这条口径;工作区未提交改动把同一口径扩展到另三个站点(`git diff` 逐行
|
|
8
|
+
核对:`md_cg/census.py`、`md_cg/mreview/locate.py`、`md_cg/rollback.py`、
|
|
9
|
+
`md_cg/mdcos.py` 的 `restore`)。本守卫把**五个读取面的行为**一并钉死——不钉
|
|
10
|
+
「源码里有 except」(源文本在场不等于行为成立),全部断言经隔离临时库真跑:
|
|
11
|
+
|
|
12
|
+
① `census.load(root)`:坏 .md 不崩、跳过;良品照常入列。
|
|
13
|
+
② `MdCGOS.restore(node_id)`:坏 trash 文件 → `ok=False` / `error=="corrupt"`,
|
|
14
|
+
且 trash 源文件**原样保留**(不删不移、逐字节不变)、节点不回索引。
|
|
15
|
+
③ `rollback.read_preimage(cg, node_id, rel)`:坏前像 → 抛 `RollbackError`
|
|
16
|
+
(`code=="preimage_unreadable"`),**不是裸 UnicodeDecodeError**;
|
|
17
|
+
相邻分支(前像缺失)→ `RollbackError(code=="preimage_missing")`。
|
|
18
|
+
④ `mreview/locate`:坏节点不崩;「在索引但不可读」与「不在索引」**分野**
|
|
19
|
+
(前者 load 非 None、blindspot 空、正文长 0、按空盘面判定;后者
|
|
20
|
+
`blindspot==["节点 X 不在索引"]` + `load=None` + `hits==[]`);批量入口
|
|
21
|
+
`locate_many` 把坏节点计入 nodes(不是 missing)并给显式读数。
|
|
22
|
+
⑤ `health()["skipped_unreadable"] == 1`(1 坏 + 2 良品库;良品照常统计)。
|
|
23
|
+
|
|
24
|
+
定点变异自证(`--mutate`,与 `md_cg/test_i50e_readside_protection.py` 同口径)
|
|
25
|
+
--------------------------------------------------------------------------
|
|
26
|
+
每处受护站点配一个定点变异(把对应捕获/计数短路掉),变异**不落盘**——
|
|
27
|
+
`inspect.getsource` + 字面替换 + `exec` + `types.FunctionType` 绑回原 globals,
|
|
28
|
+
`setattr` 安装/还原(monkeypatch 行为面)。每轮跑全部五个检查面,逐变异打印
|
|
29
|
+
「变异 <n> 红项=<k>」;判定 = 红项数逐处吻合**且**红项全部落在对应面
|
|
30
|
+
(定点性:一处变异只打红它守的那一面)。表内 expect 列为实施期实测
|
|
31
|
+
(2026-10-04 本文件首跑);锚点漂移(实现改了却没同步本表)→ 报 ANCHOR-MISS
|
|
32
|
+
并 **exit 2**(fail-closed,默认模式同样先做锚点自检,不以 git HEAD 为基线源)。
|
|
33
|
+
|
|
34
|
+
安全边界:一切在 `tempfile.mkdtemp()` 临时库内进行——绝不写工作区、绝不碰
|
|
35
|
+
任何在役记忆库(不读不写 `MDCG_ROOT`/DSH 数据根)、不起常驻服务、不重启/终止
|
|
36
|
+
任何在跑进程;变异的源码替换只在进程内存里生效,磁盘上的实现文件一字不动。
|
|
37
|
+
|
|
38
|
+
运行(仓根直跑,退出码 0 = 全绿):
|
|
39
|
+
|
|
40
|
+
python -X utf8 -m md_cg.test_corrupt_utf8_read_surfaces
|
|
41
|
+
python -X utf8 -m md_cg.test_corrupt_utf8_read_surfaces --mutate
|
|
42
|
+
python -X utf8 -m md_cg.test_corrupt_utf8_read_surfaces --mutate --list
|
|
43
|
+
"""
|
|
44
|
+
from __future__ import annotations
|
|
45
|
+
|
|
46
|
+
import inspect
|
|
47
|
+
import os
|
|
48
|
+
import shutil
|
|
49
|
+
import sys
|
|
50
|
+
import tempfile
|
|
51
|
+
import textwrap
|
|
52
|
+
import types
|
|
53
|
+
|
|
54
|
+
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
55
|
+
if REPO not in sys.path: # 保证 `import md_cg.*` 能定位到当前树
|
|
56
|
+
sys.path.insert(0, REPO)
|
|
57
|
+
|
|
58
|
+
from md_cg import census, protect, rollback # noqa: E402
|
|
59
|
+
from md_cg.mdcg import MdCG # noqa: E402
|
|
60
|
+
from md_cg.mdcos import MdCGOS # noqa: E402
|
|
61
|
+
from md_cg.mreview import locate as locate_mod # noqa: E402
|
|
62
|
+
|
|
63
|
+
#: 损坏字节:非法 UTF-8 起始字节(0xff 在任何位置都非法)。
|
|
64
|
+
BAD_BYTES = bytes((0xff, 0xfe, 0xfd))
|
|
65
|
+
#: 良品正文(CCG 六要素齐备;末尾带 \n 让盘上正文与写前逐字一致,
|
|
66
|
+
#: 消掉 `nodefile.dumps` 补行尾带来的 hash 归一噪声)。
|
|
67
|
+
GOOD_DOC = ("# 功能名:读面守卫良品节点\n"
|
|
68
|
+
"# 生效条件:任意情境\n"
|
|
69
|
+
"# 子功能:守卫夹具\n"
|
|
70
|
+
"# 执行:直接调用\n"
|
|
71
|
+
"# 验证方式:test\n"
|
|
72
|
+
"# 不适用条件:无\n"
|
|
73
|
+
"读面守卫良品正文。\n")
|
|
74
|
+
#: ④ 坏节点在 `locate_ex` 下的确定读数(实施期实测 2026-10-04,本文件首跑):
|
|
75
|
+
#: 读不到盘面 ⇒ 正文按空、fm 按空判定的五条命中(content_hash 矛盾 locator
|
|
76
|
+
#: 因 `content is None` 而**不**参与——与 locate_many 的读数差异见 ④ 末条)。
|
|
77
|
+
BAD_TRIPLES = [
|
|
78
|
+
("missing_field", "condition_space", "condition_slots"),
|
|
79
|
+
("missing_field", "content", "ccg_incomplete"),
|
|
80
|
+
("missing_field", "evidence_count", "field_absent"),
|
|
81
|
+
("missing_field", "role", "field_absent"),
|
|
82
|
+
("weak_source", "verification_basis", "basis_absent"),
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
_PASS = []
|
|
86
|
+
_FAIL = []
|
|
87
|
+
_VERBOSE = True
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _ok(cond, msg, extra=""):
|
|
91
|
+
(_PASS if cond else _FAIL).append(msg)
|
|
92
|
+
if _VERBOSE:
|
|
93
|
+
print((" PASS " if cond else " FAIL ") + msg
|
|
94
|
+
+ ((" ← " + str(extra)) if (extra and not cond) else ""))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ---------------------------------------------------------------- 夹具
|
|
98
|
+
def _synth(tmp, face, node_ids):
|
|
99
|
+
"""隔离临时库:建库 → add 全部节点(同形良品正文)→ flush。"""
|
|
100
|
+
root = os.path.join(tmp, face)
|
|
101
|
+
cg = MdCGOS(root, autoflush=0)
|
|
102
|
+
for nid in node_ids:
|
|
103
|
+
cg.add(nid, GOOD_DOC, tags=["domain:guard"])
|
|
104
|
+
cg.flush()
|
|
105
|
+
return cg, root
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _write_bad(path):
|
|
109
|
+
with open(path, "wb") as fh:
|
|
110
|
+
fh.write(BAD_BYTES)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _corrupt_node(cg, root, nid):
|
|
114
|
+
"""把某节点盘上文件替换成坏字节,返回其绝对路径。"""
|
|
115
|
+
rel = cg.index["nodes"][nid]["path"]
|
|
116
|
+
p = os.path.join(root, str(rel).replace("/", os.sep))
|
|
117
|
+
_write_bad(p)
|
|
118
|
+
return p
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------------- ① census
|
|
122
|
+
def _check_census(tmp):
|
|
123
|
+
cg, root = _synth(tmp, "census", ("good1", "good2", "bad"))
|
|
124
|
+
try:
|
|
125
|
+
_corrupt_node(cg, root, "bad")
|
|
126
|
+
rows = None
|
|
127
|
+
raised = None
|
|
128
|
+
try:
|
|
129
|
+
rows = census.load(root)
|
|
130
|
+
except BaseException as exc: # noqa: BLE001
|
|
131
|
+
raised = exc
|
|
132
|
+
_ok(raised is None, "① census.load 遇坏 .md 不崩", repr(raised))
|
|
133
|
+
ids = sorted(r[0] for r in rows or [])
|
|
134
|
+
_ok(ids == ["good1", "good2"], "① 良品照常入列(恰 good1/good2)", ids)
|
|
135
|
+
_ok("bad" not in ids, "① 坏节点被跳过(不在产物中)")
|
|
136
|
+
finally:
|
|
137
|
+
cg.close()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ---------------------------------------------------------------- ② restore
|
|
141
|
+
def _check_restore(tmp):
|
|
142
|
+
cg, root = _synth(tmp, "restore", ("good1", "bad", "victim"))
|
|
143
|
+
try:
|
|
144
|
+
_corrupt_node(cg, root, "bad")
|
|
145
|
+
_ok(cg.forget("victim").get("ok") is True, "② 夹具:victim 已软删(进 trash)")
|
|
146
|
+
tp = os.path.join(cg.trash_dir, "victim.md")
|
|
147
|
+
_ok(os.path.exists(tp), "② 夹具:trash 源文件在位")
|
|
148
|
+
_write_bad(tp)
|
|
149
|
+
before = open(tp, "rb").read()
|
|
150
|
+
out = None
|
|
151
|
+
raised = None
|
|
152
|
+
try:
|
|
153
|
+
out = cg.restore("victim", force=True)
|
|
154
|
+
except BaseException as exc: # noqa: BLE001
|
|
155
|
+
raised = exc
|
|
156
|
+
_ok(raised is None, "② 坏 trash 下 restore 不抛异常", repr(raised))
|
|
157
|
+
out = out if isinstance(out, dict) else {}
|
|
158
|
+
_ok(out.get("ok") is False, "② 返回 ok=False", out)
|
|
159
|
+
_ok(out.get("error") == "corrupt", "② error=='corrupt'", out)
|
|
160
|
+
after = open(tp, "rb").read() if os.path.exists(tp) else None
|
|
161
|
+
_ok(after == before,
|
|
162
|
+
"② trash 源文件原样保留(存在且逐字节不变)", after)
|
|
163
|
+
_ok("victim" not in cg.index["nodes"], "② 坏节点未回到索引")
|
|
164
|
+
finally:
|
|
165
|
+
cg.close()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# ---------------------------------------------------------------- ③ read_preimage
|
|
169
|
+
def _check_preimage(tmp):
|
|
170
|
+
cg, root = _synth(tmp, "preimage", ("good1", "bad"))
|
|
171
|
+
try:
|
|
172
|
+
_corrupt_node(cg, root, "bad")
|
|
173
|
+
rel = protect.snapshot_preimage(cg, "good1", action="C", pid="guard",
|
|
174
|
+
reason="守卫夹具")
|
|
175
|
+
_ok(bool(rel), "③ 夹具:前像已拍(真走 snapshot_preimage)")
|
|
176
|
+
pp = os.path.join(root, str(rel or "").replace("/", os.sep))
|
|
177
|
+
_write_bad(pp)
|
|
178
|
+
|
|
179
|
+
exc = None
|
|
180
|
+
try:
|
|
181
|
+
rollback.read_preimage(cg, "good1", rel)
|
|
182
|
+
except BaseException as e: # noqa: BLE001
|
|
183
|
+
exc = e
|
|
184
|
+
_ok(isinstance(exc, rollback.RollbackError),
|
|
185
|
+
"③ 坏前像抛 RollbackError", repr(exc))
|
|
186
|
+
_ok(not isinstance(exc, UnicodeDecodeError),
|
|
187
|
+
"③ 不是裸 UnicodeDecodeError")
|
|
188
|
+
_ok(getattr(exc, "code", None) == "preimage_unreadable",
|
|
189
|
+
"③ code=='preimage_unreadable'", getattr(exc, "code", None))
|
|
190
|
+
|
|
191
|
+
exc2 = None
|
|
192
|
+
try:
|
|
193
|
+
rollback.read_preimage(cg, "good1",
|
|
194
|
+
"_protected_history/good1/absent.md")
|
|
195
|
+
except BaseException as e: # noqa: BLE001
|
|
196
|
+
exc2 = e
|
|
197
|
+
_ok(isinstance(exc2, rollback.RollbackError)
|
|
198
|
+
and getattr(exc2, "code", None) == "preimage_missing",
|
|
199
|
+
"③ 相邻分支:前像缺失 → RollbackError(preimage_missing)", repr(exc2))
|
|
200
|
+
finally:
|
|
201
|
+
cg.close()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# ---------------------------------------------------------------- ④ locate
|
|
205
|
+
def _check_locate(tmp):
|
|
206
|
+
cg, root = _synth(tmp, "locate", ("good1", "good2", "bad"))
|
|
207
|
+
try:
|
|
208
|
+
_corrupt_node(cg, root, "bad")
|
|
209
|
+
ex = None
|
|
210
|
+
raised = None
|
|
211
|
+
try:
|
|
212
|
+
ex = locate_mod.locate_ex("bad", root=root, index=cg.index)
|
|
213
|
+
except BaseException as exc: # noqa: BLE001
|
|
214
|
+
raised = exc
|
|
215
|
+
_ok(raised is None, "④ locate_ex 对坏节点不崩", repr(raised))
|
|
216
|
+
ex = ex if isinstance(ex, dict) else {}
|
|
217
|
+
_ok(sorted(ex) == ["blindspot", "hits", "load"],
|
|
218
|
+
"④ 返回三键契约(hits/blindspot/load)", sorted(ex))
|
|
219
|
+
load = ex.get("load")
|
|
220
|
+
_ok(isinstance(load, dict) and load.get("content_len") == 0,
|
|
221
|
+
"④ 坏节点在索引:load 非 None 且正文长 0(≠ 不在索引)", load)
|
|
222
|
+
_ok(ex.get("blindspot") == [], "④ 不误报「不在索引」", ex.get("blindspot"))
|
|
223
|
+
triples = sorted((h.get("issue_kind"), h.get("field"), h.get("rule"))
|
|
224
|
+
for h in ex.get("hits") or [])
|
|
225
|
+
_ok(triples == BAD_TRIPLES,
|
|
226
|
+
"④ 按空盘面判定(实测命中集合,不静默放过)", triples)
|
|
227
|
+
_ok(all(h.get("status") == "located" for h in ex.get("hits") or []),
|
|
228
|
+
"④ 命中全部 status=='located'")
|
|
229
|
+
|
|
230
|
+
gh = None
|
|
231
|
+
raised = None
|
|
232
|
+
try:
|
|
233
|
+
gh = locate_mod.locate_ex("ghost", root=root, index=cg.index)
|
|
234
|
+
except BaseException as exc: # noqa: BLE001
|
|
235
|
+
raised = exc
|
|
236
|
+
_ok(raised is None and isinstance(gh, dict)
|
|
237
|
+
and gh.get("blindspot") == ["节点 ghost 不在索引"]
|
|
238
|
+
and gh.get("load") is None and gh.get("hits") == [],
|
|
239
|
+
"④ 相邻分支:不在索引 → blindspot 点名 / load None / hits 空",
|
|
240
|
+
(repr(raised), gh))
|
|
241
|
+
|
|
242
|
+
many = None
|
|
243
|
+
raised = None
|
|
244
|
+
try:
|
|
245
|
+
many = locate_mod.locate_many(node_ids=["bad", "good1"],
|
|
246
|
+
root=root, index=cg.index)
|
|
247
|
+
except BaseException as exc: # noqa: BLE001
|
|
248
|
+
raised = exc
|
|
249
|
+
_ok(raised is None, "④ locate_many 对坏节点不崩", repr(raised))
|
|
250
|
+
many = many if isinstance(many, dict) else {}
|
|
251
|
+
_ok(many.get("nodes") == 2 and many.get("missing") == [],
|
|
252
|
+
"④ locate_many:坏节点计入 nodes,不是 missing",
|
|
253
|
+
(many.get("nodes"), many.get("missing")))
|
|
254
|
+
_ok(any(h.get("issue_kind") == "contradiction"
|
|
255
|
+
and h.get("field") == "content_hash"
|
|
256
|
+
for h in many.get("hits") or []),
|
|
257
|
+
"④ locate_many:坏节点出显式读数(content_hash 矛盾)")
|
|
258
|
+
finally:
|
|
259
|
+
cg.close()
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# ---------------------------------------------------------------- ⑤ health
|
|
263
|
+
def _check_health(tmp):
|
|
264
|
+
cg, root = _synth(tmp, "health", ("good1", "good2", "bad"))
|
|
265
|
+
try:
|
|
266
|
+
_corrupt_node(cg, root, "bad")
|
|
267
|
+
h = None
|
|
268
|
+
raised = None
|
|
269
|
+
try:
|
|
270
|
+
h = cg.health()
|
|
271
|
+
except BaseException as exc: # noqa: BLE001
|
|
272
|
+
raised = exc
|
|
273
|
+
_ok(raised is None, "⑤ health() 不崩", repr(raised))
|
|
274
|
+
h = h if isinstance(h, dict) else {}
|
|
275
|
+
_ok(h.get("skipped_unreadable") == 1,
|
|
276
|
+
"⑤ skipped_unreadable==1(1 坏)", h.get("skipped_unreadable"))
|
|
277
|
+
_ok(h.get("total_nodes") == 3, "⑤ total_nodes==3(索引三节点)",
|
|
278
|
+
h.get("total_nodes"))
|
|
279
|
+
_ok(sum(v.get("total", 0)
|
|
280
|
+
for v in (h.get("ccg_by_layer") or {}).values()) == 2,
|
|
281
|
+
"⑤ 良品照常统计(层总计 2)", h.get("ccg_by_layer"))
|
|
282
|
+
finally:
|
|
283
|
+
cg.close()
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
_CHECKS = (("census", _check_census), ("restore", _check_restore),
|
|
287
|
+
("preimage", _check_preimage), ("locate", _check_locate),
|
|
288
|
+
("health", _check_health))
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _run_all(tmp):
|
|
292
|
+
"""跑全部检查面,返回 ({面名: 失败数}, 通过总数)。
|
|
293
|
+
|
|
294
|
+
面内异常计 1 条失败并继续下一面(守卫不因被测面抛异常整体崩)。
|
|
295
|
+
"""
|
|
296
|
+
out = {}
|
|
297
|
+
passed = 0
|
|
298
|
+
for name, fn in _CHECKS:
|
|
299
|
+
_PASS.clear()
|
|
300
|
+
_FAIL.clear()
|
|
301
|
+
try:
|
|
302
|
+
fn(tmp)
|
|
303
|
+
except BaseException as exc: # noqa: BLE001
|
|
304
|
+
_FAIL.append("面 %s 未捕获异常 %r" % (name, exc))
|
|
305
|
+
out[name] = len(_FAIL)
|
|
306
|
+
passed += len(_PASS)
|
|
307
|
+
return out, passed
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# ---------------------------------------------------------------- 定点变异自证
|
|
311
|
+
# 表内每项:holder/attr = monkeypatch 落点;old 必须**逐字**出现在目标函数
|
|
312
|
+
# 当前源码(dedent 后)里且**唯一**;new 为短路形态;face = 应转红的面;
|
|
313
|
+
# expect = 实施期实测的红项数(2026-10-04 本文件首跑,实测构成如下):
|
|
314
|
+
# m1 红 2 = ①不崩 + ①良品集合(①「坏节点不在产物中」在 load 崩掉、产物为空时
|
|
315
|
+
# 仍成立——空集不含 bad,属该断言的固有局限,读数如实记 2);
|
|
316
|
+
# m2 红 3 = ②不抛 + ok == False + error == "corrupt"(trash 字节/未回索引
|
|
317
|
+
# 两条在裸抛下仍成立,不红);
|
|
318
|
+
# m3 红 3 = ③RollbackError + ③非 UnicodeDecodeError + ③code
|
|
319
|
+
# (③相邻分支 preimage_missing 不受影响,不红);
|
|
320
|
+
# m4 红 8 = ④不崩 + 三键 + load 非 None + blindspot 空 + 命中集合 + locate_many
|
|
321
|
+
# 不崩 + nodes/missing + content_hash 读数(④status 与 ghost 邻支两条不红:
|
|
322
|
+
# 空 hits 使 "all()" 恒真、ghost 走「索引无此项」早返回不读盘);
|
|
323
|
+
# m5 红 1 = ⑤skipped_unreadable(total_nodes 与层统计不受计数短路影响)。
|
|
324
|
+
_MUTATIONS = (
|
|
325
|
+
# m1:census.load 去掉 UnicodeDecodeError 捕获(修复前形态)→ ① 红。
|
|
326
|
+
{"n": 1, "face": "census", "holder": census, "attr": "load",
|
|
327
|
+
"old": "except (OSError, UnicodeDecodeError):", "new": "except OSError:",
|
|
328
|
+
"desc": "census.load 丢 UnicodeDecodeError 捕获", "expect": 2},
|
|
329
|
+
# m2:restore 的 except 块改成裸抛(不再落结构化失败)→ ② 红。
|
|
330
|
+
{"n": 2, "face": "restore", "holder": MdCGOS, "attr": "restore",
|
|
331
|
+
"old": "# trash 源损坏:返回结构化失败,源文件原样保留(不删不移)",
|
|
332
|
+
"new": 'raise UnicodeDecodeError("utf-8", b"\\xff", 0, 1, "mut")',
|
|
333
|
+
"desc": "restore 丢 corrupt 结构化返回(改裸抛)", "expect": 3},
|
|
334
|
+
# m3:read_preimage 的捕获子句换成 OSError(UnicodeDecodeError 逃逸)→ ③ 红。
|
|
335
|
+
{"n": 3, "face": "preimage", "holder": rollback, "attr": "read_preimage",
|
|
336
|
+
"old": "except UnicodeDecodeError as exc:",
|
|
337
|
+
"new": "except OSError as exc:",
|
|
338
|
+
"desc": "read_preimage 丢坏字节捕获", "expect": 3},
|
|
339
|
+
# m4:load_node 丢 UnicodeDecodeError 捕获 → ④ 红(其余面不受累)。
|
|
340
|
+
{"n": 4, "face": "locate", "holder": locate_mod, "attr": "load_node",
|
|
341
|
+
"old": "except (OSError, UnicodeDecodeError):", "new": "except OSError:",
|
|
342
|
+
"desc": "locate.load_node 丢 UnicodeDecodeError 捕获", "expect": 8},
|
|
343
|
+
# m5:health 的跳过计数短路 → ⑤ 红。
|
|
344
|
+
{"n": 5, "face": "health", "holder": MdCG, "attr": "health",
|
|
345
|
+
"old": "skipped_unreadable += 1", "new": "pass",
|
|
346
|
+
"desc": "health 丢 skipped_unreadable 计数", "expect": 1},
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _target_src(m):
|
|
351
|
+
return textwrap.dedent(inspect.getsource(getattr(m["holder"], m["attr"])))
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _anchor_check():
|
|
355
|
+
"""返回 ANCHOR-MISS 说明列表(空 = 全部在位且唯一)。"""
|
|
356
|
+
bad = []
|
|
357
|
+
for m in _MUTATIONS:
|
|
358
|
+
src = _target_src(m)
|
|
359
|
+
cnt = src.count(m["old"])
|
|
360
|
+
if cnt != 1:
|
|
361
|
+
bad.append("变异 %d [%s] 锚点 %r 在目标源码中出现 %d 次(需恰 1 次)"
|
|
362
|
+
% (m["n"], m["face"], m["old"], cnt))
|
|
363
|
+
return bad
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _mutate_func(func, old, new):
|
|
367
|
+
"""按字面替换重编译函数(源码不落盘)——globals 绑回原模块/类。"""
|
|
368
|
+
src = textwrap.dedent(inspect.getsource(func))
|
|
369
|
+
if src.count(old) != 1:
|
|
370
|
+
raise AssertionError("锚点不唯一:%r(%d 处)" % (old, src.count(old)))
|
|
371
|
+
ns = {}
|
|
372
|
+
exec(compile(src.replace(old, new, 1), "<mut:%s>" % func.__name__, "exec"),
|
|
373
|
+
ns)
|
|
374
|
+
maker = ns[func.__name__]
|
|
375
|
+
nf = types.FunctionType(maker.__code__, func.__globals__, func.__name__,
|
|
376
|
+
maker.__defaults__, maker.__closure__)
|
|
377
|
+
nf.__kwdefaults__ = maker.__kwdefaults__
|
|
378
|
+
nf.__qualname__ = func.__qualname__
|
|
379
|
+
return nf
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _mutate_mode(tmp):
|
|
383
|
+
global _VERBOSE
|
|
384
|
+
_VERBOSE = False
|
|
385
|
+
base, _passed = _run_all(tmp)
|
|
386
|
+
b = sum(base.values())
|
|
387
|
+
print(" 未变异基线:红项=%d%s"
|
|
388
|
+
% (b, "" if b == 0 else " ← 基线即红,变异核验无意义"))
|
|
389
|
+
bad = [] if b == 0 else ["未变异基线即失败"]
|
|
390
|
+
for m in _MUTATIONS:
|
|
391
|
+
live = getattr(m["holder"], m["attr"])
|
|
392
|
+
try:
|
|
393
|
+
setattr(m["holder"], m["attr"], _mutate_func(live, m["old"],
|
|
394
|
+
m["new"]))
|
|
395
|
+
res, _passed = _run_all(tmp)
|
|
396
|
+
finally:
|
|
397
|
+
setattr(m["holder"], m["attr"], live)
|
|
398
|
+
k = sum(res.values())
|
|
399
|
+
faces = [n for n in res if res[n]]
|
|
400
|
+
hit = (k == m["expect"] and faces == [m["face"]])
|
|
401
|
+
print(" 变异 %d [%s] %s → 红项=%d(面=%s)%s"
|
|
402
|
+
% (m["n"], m["face"], m["desc"], k, ",".join(faces) or "-",
|
|
403
|
+
"命中预期" if hit else "**不命中(预期 红项=%s 面=%s)**"
|
|
404
|
+
% (m["expect"], m["face"])))
|
|
405
|
+
if not hit:
|
|
406
|
+
bad.append("变异 %d" % m["n"])
|
|
407
|
+
print("\n定点变异自证:%s"
|
|
408
|
+
% ("PASS(五处受护站点各被对应面抓住,红项数逐处吻合、无跨面误伤)"
|
|
409
|
+
if not bad else "FAIL —— " + "、".join(bad)))
|
|
410
|
+
return 0 if not bad else 1
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
# ---------------------------------------------------------------- main
|
|
414
|
+
def main():
|
|
415
|
+
if "--list" in sys.argv:
|
|
416
|
+
print("变异表(%d 项):" % len(_MUTATIONS))
|
|
417
|
+
for m in _MUTATIONS:
|
|
418
|
+
print(" %d [%s] %s(expect 红项=%s)" % (m["n"], m["face"],
|
|
419
|
+
m["desc"], m["expect"]))
|
|
420
|
+
return 0
|
|
421
|
+
|
|
422
|
+
anchor_bad = _anchor_check()
|
|
423
|
+
if anchor_bad:
|
|
424
|
+
for b in anchor_bad:
|
|
425
|
+
print(" ANCHOR-MISS " + b)
|
|
426
|
+
print("锚点自检:FAIL(fail-closed,exit 2)——实现改了请同步本文件变异表")
|
|
427
|
+
return 2
|
|
428
|
+
print("坏 UTF-8 读取面守卫(五面)——隔离临时库,不碰工作区与在役库;"
|
|
429
|
+
"锚点自检 PASS")
|
|
430
|
+
|
|
431
|
+
tmp = tempfile.mkdtemp(prefix="corrupt_utf8_guard_")
|
|
432
|
+
print("临时根:%s" % tmp)
|
|
433
|
+
try:
|
|
434
|
+
if "--mutate" in sys.argv:
|
|
435
|
+
rc = _mutate_mode(tmp)
|
|
436
|
+
else:
|
|
437
|
+
res, passed = _run_all(tmp)
|
|
438
|
+
print("== 五面汇总 ==")
|
|
439
|
+
for name, fails in res.items():
|
|
440
|
+
print(" 面 %s:%s" % (name, "绿" if fails == 0
|
|
441
|
+
else "红(%d 项)" % fails))
|
|
442
|
+
red = sum(res.values())
|
|
443
|
+
print("\n读取面守卫:%d 通过,%d 失败" % (passed, red))
|
|
444
|
+
rc = 1 if red else 0
|
|
445
|
+
finally:
|
|
446
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
447
|
+
return rc
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
if __name__ == "__main__":
|
|
451
|
+
sys.exit(main())
|
|
@@ -19,6 +19,14 @@ paths.json 直值面)改走该辅助。非 Windows 平台 ``\\\\.\\aux`` 是
|
|
|
19
19
|
承接面:``mcp_server.main()`` 启动早期对 root/aux 各探一次,ValueError →
|
|
20
20
|
stderr 告警 + return 2(对照 `_md_cg_` 前缀守卫先例「启动即拒绝,不带病运行」)。
|
|
21
21
|
|
|
22
|
+
issue #57 加固(判据与 OS 行为解耦):较新 Windows 构建的 GetFullPathNameW 不再把
|
|
23
|
+
「<目录>\\aux」吞成 ``\\.\aux``,前缀判据随之失效(报告人红单:aux/con/com1/lpt1
|
|
24
|
+
漏判、nul 未漏,本机旧构建仍吞、复现不了该红)——``md_cg/datapath.py`` 新增
|
|
25
|
+
``_RESERVED_DEVICE_NAMES`` 与纯函数 ``_reserved_device_tail``(末段直判),
|
|
26
|
+
``_abs_host_path`` 条件改为「设备前缀判据 或 末段直判」两因并置。§7 谓词表直调
|
|
27
|
+
纯函数(平台无关闭集);§8 把模块所用 ``os.path.abspath`` 临时换成恒等函数、模拟
|
|
28
|
+
报告人系统复现漏判现场——删掉「或末段直判」后 §8 在 Windows 上必转红(变异自证锚点)。
|
|
29
|
+
|
|
22
30
|
隔离纪律:四个覆盖键(MDCG_STATE_ROOT/MDCG_DATA_ROOT/MDCG_ROOT/MDCG_AUX_ROOT)
|
|
23
31
|
与 DSH_HOME 以 os.environ 副本注入、finally 逐键还原;探针路径全部是
|
|
24
32
|
tempfile.mkdtemp 下**不存在**的纯字符串(或正常临时子目录),不创建任何含
|
|
@@ -40,10 +48,12 @@ except AttributeError:
|
|
|
40
48
|
pass
|
|
41
49
|
|
|
42
50
|
from .datapath import (ENV_AUX_ROOT, ENV_DATA_ROOT, ENV_MDCG_ROOT,
|
|
43
|
-
ENV_STATE_ROOT,
|
|
44
|
-
state_root)
|
|
51
|
+
ENV_STATE_ROOT, _reserved_device_tail, aux_root,
|
|
52
|
+
data_root, mdcg_root, state_root)
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
# 断言总数下限(防假绿):含 [7] 谓词表 35 条 + [8] 现场模拟 2 条,
|
|
55
|
+
# 按新增断言数上调后仍留 ≥8 条余量。
|
|
56
|
+
FLOOR = 50
|
|
47
57
|
PASS, FAIL = 0, 0
|
|
48
58
|
|
|
49
59
|
#: 守卫注入/还原面(含 DSH_HOME——state_root 的第二档,一并与真实环境隔离)
|
|
@@ -182,8 +192,57 @@ def main():
|
|
|
182
192
|
"_probe_root" in mwin and "_probe_aux" in mwin
|
|
183
193
|
and "except ValueError" in mwin and "return 2" in mwin,
|
|
184
194
|
"main() 首段缺承接受理")
|
|
195
|
+
check("5g _abs_host_path 源窗口含 _reserved_device_tail( 调用"
|
|
196
|
+
"(末段直判与设备前缀判据并置,issue #57)",
|
|
197
|
+
"_reserved_device_tail(" in _func_window(src, "_abs_host_path"))
|
|
198
|
+
|
|
199
|
+
# ---- 7. 末段直判谓词表(issue #57):_reserved_device_tail 纯函数窗口 ----
|
|
200
|
+
# 谓词表取自实测闭集:会被吞的形态必须全判真(含变形 aux.txt/aux.tar.gz/
|
|
201
|
+
# "aux."/"aux "(尾空格)/aux..b 与绝对路径末段/带尾分隔符形态);不吞的
|
|
202
|
+
# 形态必须全判假——com0/lpt0/com10/lpt10 数字越界、auxx/auxiliary 非设备名、
|
|
203
|
+
# ".aux" 名字以点开头不拆扩展名。纯函数不触盘、无平台分支,Windows/POSIX 同判。
|
|
204
|
+
print("[7] 末段直判谓词表(_reserved_device_tail,纯字符串·平台无关)")
|
|
205
|
+
for case in ("aux", "AUX", "Aux", "aux.txt", "aux.tar.gz", "aux.",
|
|
206
|
+
"aux ", "aux..b", "con", "prn", "nul", "com1", "com9",
|
|
207
|
+
"lpt1", "lpt9", "com\u00b9", "com\u00b2", "com\u00b3",
|
|
208
|
+
"lpt\u00b9", "lpt\u00b2", "lpt\u00b3", "conin$", "conout$",
|
|
209
|
+
"D:\\probe\\aux", "D:\\probe\\aux\\", "D:\\probe\\aux.txt"):
|
|
210
|
+
check(f"7·真·{case!r}: 判为保留设备名末段(须拦)",
|
|
211
|
+
_reserved_device_tail(case), "判为 False(漏拦)")
|
|
212
|
+
for case in ("com0", "lpt0", "com10", "lpt10", "auxx", ".aux",
|
|
213
|
+
"auxiliary", "", "D:\\probe\\com0"):
|
|
214
|
+
check(f"7·假·{case!r}: 不判保留设备名末段(须放行)",
|
|
215
|
+
not _reserved_device_tail(case), "判为 True(过拦)")
|
|
216
|
+
|
|
217
|
+
# ---- 8. issue #57 现场模拟:abspath 不改写绝对路径(新 Windows 构建)----
|
|
218
|
+
# 报告人系统(较新 Windows 构建)的 GetFullPathNameW 不再把「<目录>\aux」
|
|
219
|
+
# 吞成 \\.\aux——既有前缀判据(r.startswith("\\\\.\\"))随之失效(本机旧
|
|
220
|
+
# 构建仍吞、复现不了该红,故以恒等替换模拟)。本段把 datapath 模块所用的
|
|
221
|
+
# os.path.abspath 临时换成恒等函数(保存/还原,finally),模拟「abspath
|
|
222
|
+
# 原样返回绝对路径」的报告人行为,再经 _probe_reject 探 aux_root。
|
|
223
|
+
# 变异自证锚点:删掉 _abs_host_path 条件里的 `or _reserved_device_tail(r)`
|
|
224
|
+
# 后,本段在 Windows 上必须转红(r 不以 \\.\ 开头 → 不抛 → 8a/8b 失败),
|
|
225
|
+
# 而 [7] 谓词表保持全绿(证明末段判定是承重件而非装饰)。
|
|
226
|
+
print("[8] issue #57 现场模拟:abspath 恒等(新构建不吞设备名)")
|
|
227
|
+
_abspath_orig = os.path.abspath
|
|
228
|
+
try:
|
|
229
|
+
os.path.abspath = lambda p: p
|
|
230
|
+
raised, msg = _probe_reject(aux_root, ENV_AUX_ROOT, "aux", tmp)
|
|
231
|
+
finally:
|
|
232
|
+
os.path.abspath = _abspath_orig
|
|
233
|
+
if os.name == "nt":
|
|
234
|
+
check("8a abspath 不改写时 aux_root() 仍抛 ValueError(末段直判承重)",
|
|
235
|
+
raised, f"msg={msg[:160]!r}")
|
|
236
|
+
check("8b 该路径下消息仍含覆盖键名 MDCG_AUX_ROOT",
|
|
237
|
+
ENV_AUX_ROOT in msg, f"msg={msg[:160]!r}")
|
|
238
|
+
else:
|
|
239
|
+
check("8a 非 Windows 不判定:abspath 恒等后仍不抛(平台闸未越界)",
|
|
240
|
+
not raised, f"msg={msg[:160]!r}")
|
|
241
|
+
check("8b 非 Windows 不判定:不产生拒因(消息为空)",
|
|
242
|
+
msg == "", f"msg={msg[:160]!r}")
|
|
185
243
|
|
|
186
244
|
# ---- 6. 守卫自检 floor ----
|
|
245
|
+
# [6] 后置于 [7][8] 之后:FLOOR 随新增断言数上调,须统计全部断言。
|
|
187
246
|
print("[6] 守卫自检")
|
|
188
247
|
check(f"6a 断言总数 ≥ {FLOOR}(防探针面失效假绿)",
|
|
189
248
|
PASS + FAIL >= FLOOR, f"total={PASS + FAIL}")
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
模块顶层**——只在函数体内(顶层调用会与函数内延迟导入的避环设计冲突)。
|
|
20
20
|
五 键集对拍:对临时 root 上的 `cg(op=info)` 真调一次 MCP 服务,响应必须含
|
|
21
21
|
code_generation / disk_generation / stale_on_disk / restart_required / hint
|
|
22
|
-
|
|
22
|
+
五个新键,且**既有键集合**与读码钉住的 20 键(18 既有 + issue #53-B 扩键
|
|
23
|
+
bucket_scope + PR #54 扩键 skipped_unreadable)逐字一致。
|
|
23
24
|
|
|
24
25
|
安全边界:一切在 `tempfile.mkdtemp()` 临时目录内进行(md_cg 树是复制出来的副本),
|
|
25
26
|
绝不写工作区、绝不写任何在役记忆库、绝不起用真 root 的常驻服务、绝不重启/终止
|
|
@@ -39,13 +40,17 @@ REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
39
40
|
if REPO not in sys.path: # 保证 `import md_cg.*` 能定位到当前树的包
|
|
40
41
|
sys.path.insert(0, REPO)
|
|
41
42
|
|
|
42
|
-
#
|
|
43
|
-
# 2026-09-30
|
|
43
|
+
# 钉住的 cg(op=info) 既有键集合(在临时 root 上实跑一次服务所读):
|
|
44
|
+
# 18 键为 2026-09-30 接线前读码名单;bucket_scope 为 issue #53-B(2026-10-03)
|
|
45
|
+
# 的**有意扩键**(分桶读数覆盖面自述,纯增量、与既有键无冲突,与代校验五键
|
|
46
|
+
# 同构的扩键流程);skipped_unreadable 为 PR #54(2026-10-04)的**有意扩键**
|
|
47
|
+
# (health() 跳过损坏不可读节点的显式计数,同为纯增量、与既有键无冲突的扩键
|
|
48
|
+
# 流程)——名单同步至此;其后任何未声明的漂移仍被 extra/lost 两向抓出。
|
|
44
49
|
PINNED_INFO_KEYS = frozenset({
|
|
45
|
-
"action_source", "audit_kinds", "buckets", "
|
|
46
|
-
"external_verifiers", "links", "neg_memory_counts", "ok",
|
|
47
|
-
"
|
|
48
|
-
"write_policy",
|
|
50
|
+
"action_source", "audit_kinds", "buckets", "bucket_scope", "ccg_by_layer",
|
|
51
|
+
"ccg_contract", "external_verifiers", "links", "neg_memory_counts", "ok",
|
|
52
|
+
"os", "reason", "security", "skipped_unreadable", "surface", "theory",
|
|
53
|
+
"tools", "total_nodes", "whoami", "write_policy",
|
|
49
54
|
})
|
|
50
55
|
|
|
51
56
|
NEW_INFO_KEYS = ("code_generation", "disk_generation", "stale_on_disk",
|
|
@@ -130,7 +135,7 @@ _STALE_SNIPPET = (
|
|
|
130
135
|
"import io, json, os\n"
|
|
131
136
|
"from md_cg import generation as g\n"
|
|
132
137
|
"target = os.path.join(os.path.dirname(g.__file__), 'fsutil.py')\n"
|
|
133
|
-
"raw = io.open(target, encoding='utf-8').read()\n"
|
|
138
|
+
"raw = io.open(target, encoding='utf-8', newline='').read()\n"
|
|
134
139
|
"clean = g.is_stale()\n"
|
|
135
140
|
"io.open(target, 'w', encoding='utf-8', newline='').write(raw + '\\n# 守卫临时改动\\n')\n"
|
|
136
141
|
"modified = g.is_stale()\n"
|
|
@@ -172,8 +177,11 @@ def check_verify_and_mutation(tmp):
|
|
|
172
177
|
_ok("一·全绿", "verify_delegations() 通过,且副本与当前树同代 %s" % real_fp)
|
|
173
178
|
|
|
174
179
|
# 二:定点变异——改名 mdcg.py 里的 mint_auto_id 定义
|
|
180
|
+
# 读写都 newline=""(字节忠实):恢复必须逐字节复原——默认读会把 CRLF
|
|
181
|
+
# 压成 LF、再以 newline="" 写回即静默改变文件(2026-10-05 复核定因:
|
|
182
|
+
# 该非对称曾使「三·判别力」在 CRLF 工作区下必红)。
|
|
175
183
|
mdcg_path = os.path.join(tmp, "md_cg", "mdcg.py")
|
|
176
|
-
with open(mdcg_path, encoding="utf-8") as fh:
|
|
184
|
+
with open(mdcg_path, encoding="utf-8", newline="") as fh:
|
|
177
185
|
original = fh.read()
|
|
178
186
|
mutated = original.replace("def mint_auto_id(", "def mint_auto_id_renamed(", 1)
|
|
179
187
|
if mutated == original:
|
|
@@ -325,8 +333,9 @@ def check_info_keys(tmp):
|
|
|
325
333
|
if "重启常驻 MCP 进程" not in (info["hint"] or ""):
|
|
326
334
|
_fail("五·键集", "hint 未含处置指引:%r" % (info["hint"],))
|
|
327
335
|
return
|
|
328
|
-
_ok("五·键集", "五新键齐备、既有 18
|
|
329
|
-
"
|
|
336
|
+
_ok("五·键集", "五新键齐备、既有 20 键(18 + #53-B 扩键 bucket_scope + "
|
|
337
|
+
"PR #54 扩键 skipped_unreadable)一字未动;"
|
|
338
|
+
"代 %s(code==disk,restart_required=False)"
|
|
330
339
|
% info["code_generation"])
|
|
331
340
|
|
|
332
341
|
|