@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,656 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""md_cg · issue50-e 守卫:读面保护只认显式来源(is_protected 的来源资格闸)
|
|
3
|
+
|
|
4
|
+
缺陷(编排侧已核 + 本守卫改前探针实测,非推断)
|
|
5
|
+
--------------------------------------------------
|
|
6
|
+
issue50-d(commit a33682eb)落盘了启发式真分:remember_gated 的启发式路径
|
|
7
|
+
importance=0.7x + importance_source="heuristic" 进 fm 与索引条目,**不打**
|
|
8
|
+
fm.protected 位(写侧 add 的 source 闸,mdcg.py:2453-2455)。但读面
|
|
9
|
+
`protect.is_protected` 的按分自动保护分支(原 :145-150)**不看来源**——
|
|
10
|
+
`imp >= AUTO_PROTECT_IMPORTANCE` 即 True(reason「重要性保护」),启发式
|
|
11
|
+
0.775 节点被读面认回受保护:写侧「只落分不打位」被读侧单方面推翻。
|
|
12
|
+
改前探针实测(隔离临时根合成库,2026-10-02):a_heur(0.775/heuristic/无位)
|
|
13
|
+
is_protected=True「重要性保护:importance=0.78≥0.7」。
|
|
14
|
+
|
|
15
|
+
本批修复(使用者裁定 2026-10-02:读面也只认显式来源)
|
|
16
|
+
--------------------------------------------------
|
|
17
|
+
`is_protected` 的按分分支改为:**仅当 fm.importance_source 键缺省(None)或
|
|
18
|
+
== "hint" 时**才可由 importance 分数触发;"heuristic" 等显式非 hint 来源
|
|
19
|
+
不得由分数触发(落到返回 False,除非命中层保护或 fm.protected 位)。边界:
|
|
20
|
+
① 键缺省 = 存量节点(既有 cg.add(importance=0.9) 直写、维护路径调分等
|
|
21
|
+
从未有过该键)⇒ 行为一字不变——按「缺省即不认」会大规模改变既有保护
|
|
22
|
+
面,禁止;
|
|
23
|
+
② 新写入的启发式节点恒带该键 ⇒ 本语义恰好只把「机器推断的重要度」从
|
|
24
|
+
按分保护里摘出去;
|
|
25
|
+
③ 层保护(PROTECTED_LAYERS)、fm.protected 位、is_immutable 一律不动;
|
|
26
|
+
④ MERGE 强化(forgetting.reinforce 跨 0.7 置 protected=True)是「重复
|
|
27
|
+
确认」的显式动作,不经本分支,不受影响。
|
|
28
|
+
⑤ 不改 add() 的写侧 source 闸(issue50-d 已落)、不改遗忘判据、不改检索面;
|
|
29
|
+
fm 形态零新增字段(importance_source 键 issue50-d 已落,读面只是消费)。
|
|
30
|
+
|
|
31
|
+
改前/改后对拍表(E 组;同一组用例改前/改后两棵树各跑一遍的实测读数)
|
|
32
|
+
------------------------------------------------------------------------
|
|
33
|
+
本表是**实施期一次性实测的字面量**,不是 git HEAD 引用(基线绑提交即失效,
|
|
34
|
+
本仓已有两次教训)——改前树用临时探针跑出(Temp/i50e_probe_before.py),
|
|
35
|
+
改后由本守卫的 E 组断言钉住。「期望仅 heuristic 行变」实测成立。
|
|
36
|
+
|
|
37
|
+
| 节点(importance/source/protected) | is_protected | guard_forget | guard_move | guard_overwrite | check.protected | stats 计入 |
|
|
38
|
+
|--------------------------------------|--------------|--------------|------------|-----------------|-----------------|------------|
|
|
39
|
+
| a_heur(0.775/heuristic/无位) | True「重要性保护:importance=0.78≥0.7」→False""【变】| 拦→放【变】| 拦→放【变】| 放→放【不变】| true→false【变】| 计→不计【变】|
|
|
40
|
+
| b_legacy(0.85/无键/无位) | True「重要性保护:importance=0.85≥0.7」逐字同【不变】| 拦【不变】| 拦【不变】| 放【不变】| true【不变】| 计【不变】|
|
|
41
|
+
| c_hint(0.9/hint/位True) | True「importance=0.90≥0.7」逐字同【不变】| 拦【不变】| 拦【不变】| 放【不变】| true【不变】| 计【不变】|
|
|
42
|
+
| d_self(0.775/heuristic/self层) | True「层保护:self(不可遗忘层)」【不变】| 拦【不变】| 拦【不变】| 拦【不变】| true【不变】| 计【不变】|
|
|
43
|
+
| stats 聚合(含 a_ref=0.775/无键/无位 对照,5 节点)| protected_count 5→4【变】auto_by_importance 4→3【变】||||
|
|
44
|
+
| scrub._apply_offset(只含 a_heur) | (adjusted,skipped) (0,1)→(1,0)【变:启发式不再免疫净化校准】||||
|
|
45
|
+
注:guard_overwrite 走 is_immutable(只认层/位,不认分)⇒ 与本语义无交集,
|
|
46
|
+
三类节点改前改后均放行——「不再要求 override」落地为「无 override 放行」
|
|
47
|
+
的恒真锚(契约 A),列标【不变】。
|
|
48
|
+
|
|
49
|
+
调用方枚举(F 组静态枚举实测:4 文件、非注释/非 def 的 is_protected( 调用 7 处)
|
|
50
|
+
------------------------------------------------------------------------
|
|
51
|
+
protect.guard_forget(:262) / protect.guard_move(:274) / protect.stats(:408)
|
|
52
|
+
/ mcp_server._protect_call action=check(:1216)(protect.check 工具读面)
|
|
53
|
+
/ scrub.apply 的 skip_protected 判定(:665) / scrub._apply_offset 的 skip
|
|
54
|
+
判定(:728) / self_state.check 的 8 保护一致(:865,判据同 is_protected,
|
|
55
|
+
状态卡为 self 层 ⇒ 层保护先行,经 d_self 钉住)。
|
|
56
|
+
逐类读数(三类节点 × 调用方)见 E 表与 F2 行读数——启发式行:遗忘/搬迁
|
|
57
|
+
放行、check 读 false、净化与校准不再 skip、stats 退出(含 auto 分类);
|
|
58
|
+
存量/hint/self 行全部一字不变。
|
|
59
|
+
|
|
60
|
+
定点变异自证(--mutate)
|
|
61
|
+
------------------------
|
|
62
|
+
每一处判据都配一个定点变异把它打红,且**恰好**命中预期红项数
|
|
63
|
+
(_SRC_MUTATIONS 的 expected 列为实施期实测;计数不符即判 FAIL——照
|
|
64
|
+
test_i50d_importance_source.py / test_neg_condition_hits.py 同口径):
|
|
65
|
+
m1「把 heuristic 也认保护」→ A 组启发式读数红(存量/位面不红——闸只加宽
|
|
66
|
+
heuristic 一态);
|
|
67
|
+
m2「把缺省键也判不认」→ B 组存量零回归锚红 + A6 同分缺省对照红;
|
|
68
|
+
m3「按分整支删除」→ A/B 同红(A 经 A6 同分缺省对照——对照节点不被按分
|
|
69
|
+
保护即红;B 经存量锚),启发式行不红(启发式改后本就该 False,整支
|
|
70
|
+
删除不改变其读数——这正是对照断言 A6 存在的理由)。
|
|
71
|
+
锚点漂移(实现改了却没同步本表)→ 报 ANCHOR-MISS 并 **exit 2**(fail-closed);
|
|
72
|
+
默认模式同样先做锚点自检。**不以 git HEAD 为基线源**——变异一律作用在当前
|
|
73
|
+
盘的实现上(inspect.getsource + 字面替换 + exec,不落盘不改源文件)。
|
|
74
|
+
|
|
75
|
+
运行:python -X utf8 -m md_cg.test_i50e_readside_protection
|
|
76
|
+
python -X utf8 -m md_cg.test_i50e_readside_protection --mutate
|
|
77
|
+
"""
|
|
78
|
+
from __future__ import annotations
|
|
79
|
+
|
|
80
|
+
import contextlib
|
|
81
|
+
import inspect
|
|
82
|
+
import io
|
|
83
|
+
import os
|
|
84
|
+
import shutil
|
|
85
|
+
import subprocess
|
|
86
|
+
import sys
|
|
87
|
+
import tempfile
|
|
88
|
+
|
|
89
|
+
# 三档自治批次②(2026-10-02)**夹具隔离**:本守卫考的不是档位面(档位守卫 =
|
|
90
|
+
# md_cg/test_autonomy_modes.py),故显式置 full 档——回到改动前「动作直落」的
|
|
91
|
+
# 行为,使本文件的断言意图(合并/覆写/软删真的发生)逐条不变;env 键名从唯一
|
|
92
|
+
# 真源表取(本文件不构成第二处字面量)。
|
|
93
|
+
from . import autonomy_modes as _autonomy_modes # noqa: E402
|
|
94
|
+
os.environ[_autonomy_modes.AUTONOMY_ENV_KEYS["mode"]] = "full"
|
|
95
|
+
|
|
96
|
+
from . import mcp_server, protect, scrub
|
|
97
|
+
from .mdcg import MdCG
|
|
98
|
+
|
|
99
|
+
_PASS = []
|
|
100
|
+
_FAIL = []
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def ok(cond, msg, extra=""):
|
|
104
|
+
(_PASS if cond else _FAIL).append(msg)
|
|
105
|
+
print((" PASS " if cond else " FAIL ") + msg
|
|
106
|
+
+ ((" ← " + str(extra)) if (extra and not cond) else ""))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ---------------------------------------------------------------- 夹具
|
|
110
|
+
# 四类节点 + 同分缺省对照,全部落在隔离临时根合成库(绝不触在役数据根):
|
|
111
|
+
# a_heur 0.775 / importance_source="heuristic" / 无 protected 位
|
|
112
|
+
# —— add 直传 source="heuristic"(写侧闸 issue50-d:不打位),
|
|
113
|
+
# 与 remember_gated 启发式路径的落盘形态同构(i50d A4-A7 已钉
|
|
114
|
+
# 落盘三件套:落分=裁决值 / source=heuristic / 无位)。
|
|
115
|
+
# a_ref 0.775 / 无 source 键 / 无位 —— 同分缺省对照(判别力锚:证明
|
|
116
|
+
# A1 的 False 是「来源」导致而非分数/形态导致;m3 整支删除时它红)。
|
|
117
|
+
# b_legacy 0.85 / 无 source 键 / 无位 —— 存量形态(0.3 写入后调分,模拟
|
|
118
|
+
# 既有直写/维护路径调分的无键节点)。
|
|
119
|
+
# c_hint 0.9 / "hint" / protected=True —— 写侧对显式 hint 过线打位。
|
|
120
|
+
# d_self 0.775 / "heuristic" / self 层 —— 层保护先行(heuristic 不认也
|
|
121
|
+
# 轮不到按分分支)。
|
|
122
|
+
DOC = ("# 功能名:i50e 守卫节点\n# 生效条件:任意情境\n# 子功能:验收\n"
|
|
123
|
+
"# 执行:直接调用\n# 验证方式:test\n# 不适用条件:无\n"
|
|
124
|
+
"读面保护来源资格闸守卫正文。")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _set_fm(cg, nid, **kv):
|
|
128
|
+
"""按 scrub._apply_offset / protect.mark 同口径写回 fm(值 None = 删键),
|
|
129
|
+
同步索引条目并标脏 flush——让读面(索引)与盘面一致。"""
|
|
130
|
+
node = cg.get(nid)
|
|
131
|
+
fm = dict(node.get("frontmatter") or {})
|
|
132
|
+
for k, v in kv.items():
|
|
133
|
+
if v is None:
|
|
134
|
+
fm.pop(k, None)
|
|
135
|
+
else:
|
|
136
|
+
fm[k] = v
|
|
137
|
+
e = cg.index["nodes"].get(nid) or {}
|
|
138
|
+
path = os.path.join(cg.root, e.get("path") or node.get("path")
|
|
139
|
+
or (nid + ".md"))
|
|
140
|
+
cg._write_node(nid, path, fm, node.get("content") or "")
|
|
141
|
+
if e is not None:
|
|
142
|
+
for k in ("importance", "protected", "protection_reason",
|
|
143
|
+
"importance_source", "confidence", "immutable"):
|
|
144
|
+
e[k] = fm.get(k)
|
|
145
|
+
d = getattr(cg, "_dirty", None)
|
|
146
|
+
if isinstance(d, dict):
|
|
147
|
+
d[nid] = e
|
|
148
|
+
fl = getattr(cg, "flush", None)
|
|
149
|
+
if callable(fl):
|
|
150
|
+
try:
|
|
151
|
+
fl()
|
|
152
|
+
except Exception:
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@contextlib.contextmanager
|
|
157
|
+
def _cg(*nids):
|
|
158
|
+
"""隔离临时根上构造指定节点集合;with 出来的是 MdCG 实例(finally 清理)。"""
|
|
159
|
+
root = tempfile.mkdtemp(prefix="i50e_")
|
|
160
|
+
cg = MdCG(root)
|
|
161
|
+
try:
|
|
162
|
+
for nid in nids:
|
|
163
|
+
if nid == "a_heur":
|
|
164
|
+
cg.add(nid, DOC, layer="knowledge", importance=0.775,
|
|
165
|
+
importance_source="heuristic", verification_basis="test")
|
|
166
|
+
elif nid == "a_ref":
|
|
167
|
+
cg.add(nid, DOC, layer="knowledge", importance=0.3,
|
|
168
|
+
verification_basis="test")
|
|
169
|
+
_set_fm(cg, nid, importance=0.775) # 无 source 键、无位
|
|
170
|
+
elif nid == "b_legacy":
|
|
171
|
+
cg.add(nid, DOC, layer="knowledge", importance=0.3,
|
|
172
|
+
verification_basis="test")
|
|
173
|
+
_set_fm(cg, nid, importance=0.85) # 存量:无键、无位
|
|
174
|
+
elif nid == "c_hint":
|
|
175
|
+
cg.add(nid, DOC, layer="knowledge", importance=0.9,
|
|
176
|
+
importance_source="hint", verification_basis="test")
|
|
177
|
+
elif nid == "d_self":
|
|
178
|
+
cg.add(nid, DOC, layer="self", importance=0.775,
|
|
179
|
+
importance_source="heuristic",
|
|
180
|
+
verification_basis="test")
|
|
181
|
+
else:
|
|
182
|
+
raise KeyError(nid)
|
|
183
|
+
yield cg
|
|
184
|
+
finally:
|
|
185
|
+
try:
|
|
186
|
+
cg.close()
|
|
187
|
+
except Exception:
|
|
188
|
+
pass
|
|
189
|
+
shutil.rmtree(root, ignore_errors=True)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _guard_line(fn, *args, **kw):
|
|
193
|
+
"""守卫读数:None=放行,否则 ProtectionError 文案(逐字比对用)。"""
|
|
194
|
+
try:
|
|
195
|
+
r = fn(*args, **kw)
|
|
196
|
+
return None if r is None else ("ret:" + str(r))
|
|
197
|
+
except protect.ProtectionError as e:
|
|
198
|
+
return "ProtectionError:" + str(e)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# 改前探针实测字面量(改前树一次性实测,非 git HEAD 引用;E 组逐格钉住)
|
|
202
|
+
_BEFORE = {
|
|
203
|
+
"a_heur": {"is_protected": (True, "重要性保护:importance=0.78≥0.7"),
|
|
204
|
+
"guard_forget": "ProtectionError:节点 a_heur 不可遗忘"
|
|
205
|
+
"(重要性保护:importance=0.78≥0.7);删除需显式"
|
|
206
|
+
" override=True",
|
|
207
|
+
"guard_overwrite": None,
|
|
208
|
+
"check_protected": True},
|
|
209
|
+
"b_legacy": {"is_protected": (True, "重要性保护:importance=0.85≥0.7"),
|
|
210
|
+
"guard_forget": "ProtectionError:节点 b_legacy 不可遗忘"
|
|
211
|
+
"(重要性保护:importance=0.85≥0.7);删除需显式"
|
|
212
|
+
" override=True",
|
|
213
|
+
"guard_overwrite": None,
|
|
214
|
+
"check_protected": True},
|
|
215
|
+
"c_hint": {"is_protected": (True, "importance=0.90≥0.7"),
|
|
216
|
+
"guard_forget": "ProtectionError:节点 c_hint 不可遗忘"
|
|
217
|
+
"(importance=0.90≥0.7);删除需显式 override=True",
|
|
218
|
+
"guard_overwrite": None,
|
|
219
|
+
"check_protected": True},
|
|
220
|
+
"d_self": {"is_protected": (True, "层保护:self(不可遗忘层)"),
|
|
221
|
+
"guard_overwrite": "ProtectionError:节点 d_self 受写保护"
|
|
222
|
+
"(层保护:self(不可篡改层));覆盖需显式"
|
|
223
|
+
" override=True",
|
|
224
|
+
"check_protected": True},
|
|
225
|
+
}
|
|
226
|
+
# 改后期望(「变」列):a_heur 退出按分保护,其余逐字同改前
|
|
227
|
+
_AFTER_A_HEUR = {"is_protected": (False, ""), "check_protected": False}
|
|
228
|
+
_AFTER_STATS5 = {"protected_count": 4, "auto_by_importance": 3,
|
|
229
|
+
"ids": ["a_ref", "b_legacy", "c_hint", "d_self"]}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# ---------------------------------------------------------------- F0 夹具自检
|
|
233
|
+
def g_f0():
|
|
234
|
+
print("== F0 夹具自检(四类节点确实落在契约形态上)==")
|
|
235
|
+
with _cg("a_heur", "a_ref", "b_legacy", "c_hint", "d_self") as cg:
|
|
236
|
+
fma = (cg.get("a_heur") or {}).get("frontmatter") or {}
|
|
237
|
+
ok(fma.get("importance") == 0.775
|
|
238
|
+
and fma.get("importance_source") == "heuristic"
|
|
239
|
+
and fma.get("protected") is None,
|
|
240
|
+
"F0-1 a_heur 落盘形态:0.775/heuristic/无位(issue50-d 写侧同构)",
|
|
241
|
+
(fma.get("importance"), fma.get("importance_source"),
|
|
242
|
+
fma.get("protected")))
|
|
243
|
+
fmr = (cg.get("a_ref") or {}).get("frontmatter") or {}
|
|
244
|
+
ok(fmr.get("importance") == 0.775
|
|
245
|
+
and "importance_source" not in fmr
|
|
246
|
+
and fmr.get("protected") is None,
|
|
247
|
+
"F0-2 a_ref 落盘形态:0.775/无 source 键/无位(同分缺省对照)",
|
|
248
|
+
(fmr.get("importance"), fmr.get("importance_source"),
|
|
249
|
+
fmr.get("protected")))
|
|
250
|
+
fmb = (cg.get("b_legacy") or {}).get("frontmatter") or {}
|
|
251
|
+
ok(fmb.get("importance") == 0.85
|
|
252
|
+
and "importance_source" not in fmb
|
|
253
|
+
and fmb.get("protected") is None,
|
|
254
|
+
"F0-3 b_legacy 落盘形态:0.85/无 source 键/无位(存量)",
|
|
255
|
+
(fmb.get("importance"), fmb.get("importance_source"),
|
|
256
|
+
fmb.get("protected")))
|
|
257
|
+
fmc = (cg.get("c_hint") or {}).get("frontmatter") or {}
|
|
258
|
+
ok(fmc.get("importance") == 0.9
|
|
259
|
+
and fmc.get("importance_source") == "hint"
|
|
260
|
+
and fmc.get("protected") is True,
|
|
261
|
+
"F0-4 c_hint 落盘形态:0.9/hint/位True(写侧显式打位不受影响)",
|
|
262
|
+
(fmc.get("importance"), fmc.get("importance_source"),
|
|
263
|
+
fmc.get("protected")))
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# ---------------------------------------------------------------- A 启发式面
|
|
267
|
+
def g_a():
|
|
268
|
+
print("== A 启发式节点:读面不再按分认保护(含同分缺省对照)==")
|
|
269
|
+
with _cg("a_heur", "a_ref", "d_self") as cg:
|
|
270
|
+
ok(protect.is_protected(cg, "a_heur") == (False, ""),
|
|
271
|
+
"A1 is_protected(a_heur) == (False, '')(0.775/heuristic/无位"
|
|
272
|
+
" → 改前 (True,「重要性保护:importance=0.78≥0.7」))",
|
|
273
|
+
protect.is_protected(cg, "a_heur"))
|
|
274
|
+
ok(_guard_line(protect.guard_forget, cg, "a_heur") is None,
|
|
275
|
+
"A2 guard_forget(a_heur) 放行(改前拦:不可遗忘 ProtectionError)",
|
|
276
|
+
_guard_line(protect.guard_forget, cg, "a_heur"))
|
|
277
|
+
ok(_guard_line(protect.guard_move, cg, "a_heur", "contextual") is None,
|
|
278
|
+
"A3 guard_move(a_heur, contextual) 放行(改前拦:降级搬迁)",
|
|
279
|
+
_guard_line(protect.guard_move, cg, "a_heur", "contextual"))
|
|
280
|
+
ok(_guard_line(protect.guard_overwrite, cg, "a_heur") is None,
|
|
281
|
+
"A4 guard_overwrite(a_heur) 无 override 放行(该闸走 is_immutable"
|
|
282
|
+
" 不认分——改前改后同,恒真锚)",
|
|
283
|
+
_guard_line(protect.guard_overwrite, cg, "a_heur"))
|
|
284
|
+
chk = mcp_server._protect_call(cg, {"action": "check",
|
|
285
|
+
"node_id": "a_heur"})
|
|
286
|
+
ok(chk.get("protected") is False and chk.get("reason") == "",
|
|
287
|
+
"A5 protect.check 工具读面:protected=false / reason=''(改前 true"
|
|
288
|
+
" /「重要性保护:importance=0.78≥0.7」)", chk)
|
|
289
|
+
ok(protect.is_protected(cg, "a_ref") == (True,
|
|
290
|
+
"重要性保护:importance=0.78"
|
|
291
|
+
"≥0.7"),
|
|
292
|
+
"A6 同分缺省对照 a_ref:is_protected True 且 reason 与改前逐字同"
|
|
293
|
+
"(判别力锚——A1 的 False 是来源导致而非分数/形态导致;m3 靠它红)",
|
|
294
|
+
protect.is_protected(cg, "a_ref"))
|
|
295
|
+
with _cg("a_heur") as cg:
|
|
296
|
+
adj, skip = scrub._apply_offset(cg, 0.1, override=False,
|
|
297
|
+
min_evidence=0)
|
|
298
|
+
ok((adj, skip) == (1, 0),
|
|
299
|
+
"A7 scrub._apply_offset 不再 skip 启发式节点:(adjusted,skipped)"
|
|
300
|
+
"==(1,0)(改前 (0,1)——机器推断重要度不再免疫净化校准)",
|
|
301
|
+
(adj, skip))
|
|
302
|
+
st = protect.stats(cg)
|
|
303
|
+
ok(st["protected_count"] == 0 and st["auto_by_importance"] == 0
|
|
304
|
+
and st["ids"] == [],
|
|
305
|
+
"A8 stats(只含 a_heur):protected_count=0 / auto_by_importance=0"
|
|
306
|
+
" / ids=[](改前 1/1/[a_heur])", st)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# ---------------------------------------------------------------- B 存量面
|
|
310
|
+
def g_b():
|
|
311
|
+
print("== B 存量形态(0.85/无键/无位):零回归锚,reason 逐字同改前 ==")
|
|
312
|
+
with _cg("b_legacy") as cg:
|
|
313
|
+
ok(protect.is_protected(cg, "b_legacy")
|
|
314
|
+
== (True, "重要性保护:importance=0.85≥0.7"),
|
|
315
|
+
"B1 is_protected(b_legacy) 逐字同改前(键缺省 ⇒ 按分保护一字不变)",
|
|
316
|
+
protect.is_protected(cg, "b_legacy"))
|
|
317
|
+
ok(_guard_line(protect.guard_forget, cg, "b_legacy")
|
|
318
|
+
== _BEFORE["b_legacy"]["guard_forget"],
|
|
319
|
+
"B2 guard_forget(b_legacy) 拦且文案逐字同改前",
|
|
320
|
+
_guard_line(protect.guard_forget, cg, "b_legacy"))
|
|
321
|
+
ok(_guard_line(protect.guard_move, cg, "b_legacy", "contextual")
|
|
322
|
+
== _BEFORE["b_legacy"]["guard_forget"].replace(
|
|
323
|
+
"不可遗忘", "受写保护").replace("删除需显式", "降级移出保护层需显式"),
|
|
324
|
+
"B3 guard_move(b_legacy) 拦且文案同改前形态",
|
|
325
|
+
_guard_line(protect.guard_move, cg, "b_legacy", "contextual"))
|
|
326
|
+
ok(_guard_line(protect.guard_overwrite, cg, "b_legacy") is None,
|
|
327
|
+
"B4 guard_overwrite(b_legacy) 放行(改前改后同)",
|
|
328
|
+
_guard_line(protect.guard_overwrite, cg, "b_legacy"))
|
|
329
|
+
chk = mcp_server._protect_call(cg, {"action": "check",
|
|
330
|
+
"node_id": "b_legacy"})
|
|
331
|
+
ok(chk.get("protected") is True
|
|
332
|
+
and chk.get("reason") == "重要性保护:importance=0.85≥0.7",
|
|
333
|
+
"B5 protect.check 读面逐字同改前", chk)
|
|
334
|
+
st = protect.stats(cg)
|
|
335
|
+
ok(st["protected_count"] == 1 and st["auto_by_importance"] == 1
|
|
336
|
+
and st["ids"] == ["b_legacy"],
|
|
337
|
+
"B6 stats(只含 b_legacy):1/1/计入 auto(改前改后同)", st)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
# ---------------------------------------------------------------- C hint 面
|
|
341
|
+
def g_c():
|
|
342
|
+
print("== C 显式 hint 节点:fm.protected 位分支不受影响 ==")
|
|
343
|
+
with _cg("c_hint") as cg:
|
|
344
|
+
ok(protect.is_protected(cg, "c_hint") == (True, "importance=0.90≥0.7"),
|
|
345
|
+
"C1 is_protected(c_hint) 走 fm.protected 位分支且 reason 逐字同改前",
|
|
346
|
+
protect.is_protected(cg, "c_hint"))
|
|
347
|
+
fmc = (cg.get("c_hint") or {}).get("frontmatter") or {}
|
|
348
|
+
ok(fmc.get("protected") is True,
|
|
349
|
+
"C2 写侧显式 hint 过线打位不受本批影响", fmc.get("protected"))
|
|
350
|
+
ok(_guard_line(protect.guard_forget, cg, "c_hint")
|
|
351
|
+
== _BEFORE["c_hint"]["guard_forget"],
|
|
352
|
+
"C3 guard_forget(c_hint) 拦且文案逐字同改前",
|
|
353
|
+
_guard_line(protect.guard_forget, cg, "c_hint"))
|
|
354
|
+
ok(protect.is_immutable(cg, "c_hint") == (False, ""),
|
|
355
|
+
"C4 is_immutable(c_hint) == (False, '')(immutable 位零变化)",
|
|
356
|
+
protect.is_immutable(cg, "c_hint"))
|
|
357
|
+
st = protect.stats(cg)
|
|
358
|
+
ok(st["protected_count"] == 1 and st["auto_by_importance"] == 1,
|
|
359
|
+
"C5 stats(只含 c_hint):位 reason 含 importance= ⇒ 依旧计 auto"
|
|
360
|
+
"(改前改后同)", st)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
# ---------------------------------------------------------------- D 层保护/不可覆盖
|
|
364
|
+
def g_d():
|
|
365
|
+
print("== D 层保护与 is_immutable 零变化(钉既有断言跑通而非重写)==")
|
|
366
|
+
with _cg("a_heur", "b_legacy", "c_hint", "d_self") as cg:
|
|
367
|
+
ok(protect.is_protected(cg, "d_self")
|
|
368
|
+
== (True, "层保护:self(不可遗忘层)"),
|
|
369
|
+
"D1 d_self(heuristic + self 层):层保护先于按分分支,reason 逐字"
|
|
370
|
+
"同改前", protect.is_protected(cg, "d_self"))
|
|
371
|
+
ok(protect.is_immutable(cg, "d_self")
|
|
372
|
+
== (True, "层保护:self(不可篡改层)"),
|
|
373
|
+
"D2 is_immutable(d_self) 层保护逐字同改前",
|
|
374
|
+
protect.is_immutable(cg, "d_self"))
|
|
375
|
+
ok(all(protect.is_immutable(cg, n) == (False, "")
|
|
376
|
+
for n in ("a_heur", "b_legacy", "c_hint")),
|
|
377
|
+
"D3 knowledge 层三节点 is_immutable 全 (False, '')(不可覆盖闸"
|
|
378
|
+
"零变化——不认分数也不认来源)",
|
|
379
|
+
[protect.is_immutable(cg, n) for n in
|
|
380
|
+
("a_heur", "b_legacy", "c_hint")])
|
|
381
|
+
ok(_guard_line(protect.guard_overwrite, cg, "d_self")
|
|
382
|
+
== _BEFORE["d_self"]["guard_overwrite"],
|
|
383
|
+
"D4 guard_overwrite(d_self) 拦且文案逐字同改前(层保护经"
|
|
384
|
+
" is_immutable 生效)",
|
|
385
|
+
_guard_line(protect.guard_overwrite, cg, "d_self"))
|
|
386
|
+
ok("importance_source" not in inspect.getsource(protect.is_immutable),
|
|
387
|
+
"D5 静态:is_immutable 源码零改动(不含 importance_source)")
|
|
388
|
+
env = dict(os.environ)
|
|
389
|
+
env["PYTHONUTF8"] = "1"
|
|
390
|
+
repo = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
391
|
+
proc = subprocess.run(
|
|
392
|
+
[sys.executable, "-X", "utf8", "-m", "md_cg.test_p9_forget_protect"],
|
|
393
|
+
capture_output=True, text=True, encoding="utf-8", errors="replace",
|
|
394
|
+
cwd=repo, env=env, timeout=300)
|
|
395
|
+
ok(proc.returncode == 0,
|
|
396
|
+
"D6 既有保护族守卫 test_p9_forget_protect 不改断言跑通(rc=0)",
|
|
397
|
+
proc.stdout[-400:] + proc.stderr[-200:])
|
|
398
|
+
proc2 = subprocess.run(
|
|
399
|
+
[sys.executable, "-X", "utf8", "-m",
|
|
400
|
+
"md_cg.test_i50d_importance_source"],
|
|
401
|
+
capture_output=True, text=True, encoding="utf-8", errors="replace",
|
|
402
|
+
cwd=repo, env=env, timeout=300)
|
|
403
|
+
ok(proc2.returncode == 0,
|
|
404
|
+
"D7 写侧守卫 test_i50d_importance_source 不改断言跑通(rc=0)",
|
|
405
|
+
proc2.stdout[-400:] + proc2.stderr[-200:])
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
# ---------------------------------------------------------------- E 对拍锚
|
|
409
|
+
def g_e():
|
|
410
|
+
print("== E 改前/改后对拍锚(docstring 表逐格:变列确实变、不变列逐字同)==")
|
|
411
|
+
with _cg("a_heur", "a_ref", "b_legacy", "c_hint", "d_self") as cg:
|
|
412
|
+
pa = protect.is_protected(cg, "a_heur")
|
|
413
|
+
ok(pa == _AFTER_A_HEUR["is_protected"]
|
|
414
|
+
and pa != _BEFORE["a_heur"]["is_protected"],
|
|
415
|
+
"E1 a_heur 行变列:is_protected True→(False, '')(确实变了)", pa)
|
|
416
|
+
ok(_guard_line(protect.guard_forget, cg, "a_heur") is None,
|
|
417
|
+
"E2 a_heur 行变列:guard_forget 拦→放", "")
|
|
418
|
+
chk_a = mcp_server._protect_call(cg, {"action": "check",
|
|
419
|
+
"node_id": "a_heur"})
|
|
420
|
+
ok(chk_a.get("protected") is False
|
|
421
|
+
and _BEFORE["a_heur"]["check_protected"] is True,
|
|
422
|
+
"E3 a_heur 行变列:check.protected true→false", chk_a)
|
|
423
|
+
for nid in ("b_legacy", "c_hint", "d_self"):
|
|
424
|
+
p = protect.is_protected(cg, nid)
|
|
425
|
+
ok(p == _BEFORE[nid]["is_protected"],
|
|
426
|
+
"E4 %s 行不变列:is_protected 与改前逐字相同" % nid, p)
|
|
427
|
+
ok(_guard_line(protect.guard_overwrite, cg, nid)
|
|
428
|
+
== _BEFORE[nid]["guard_overwrite"],
|
|
429
|
+
"E5 %s 行不变列:guard_overwrite 与改前相同" % nid, "")
|
|
430
|
+
chk = mcp_server._protect_call(cg, {"action": "check",
|
|
431
|
+
"node_id": nid})
|
|
432
|
+
ok(chk.get("protected") == _BEFORE[nid]["check_protected"],
|
|
433
|
+
"E6 %s 行不变列:check.protected 与改前相同" % nid, chk)
|
|
434
|
+
st = protect.stats(cg)
|
|
435
|
+
ok(st["protected_count"] == _AFTER_STATS5["protected_count"]
|
|
436
|
+
and st["auto_by_importance"] == _AFTER_STATS5["auto_by_importance"]
|
|
437
|
+
and st["ids"] == _AFTER_STATS5["ids"]
|
|
438
|
+
and st["protected_count"] != 5 and st["auto_by_importance"] != 4,
|
|
439
|
+
"E7 stats 聚合变列:5 节点 protected_count 5→4、"
|
|
440
|
+
"auto_by_importance 4→3(heuristic 退出是预期,不是回归)", st)
|
|
441
|
+
ok(_BEFORE["a_heur"]["is_protected"][0] is True,
|
|
442
|
+
"E8 改前锚自检:a_heur 改前确实 True(缺陷形态,探针实测字面)")
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
# ---------------------------------------------------------------- F 调用方枚举与逐类读数
|
|
446
|
+
_CALLSITES = {
|
|
447
|
+
"protect.py": (3, "guard_forget:262 / guard_move:274 / stats:408"),
|
|
448
|
+
"mcp_server.py": (1, "_protect_call action=check:1216"),
|
|
449
|
+
"scrub.py": (2, "apply skip_protected:665 / _apply_offset skip:728"),
|
|
450
|
+
"self_state.py": (1, "check 8 保护一致:865"),
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def g_f():
|
|
455
|
+
print("== F 调用方枚举与逐类读数 ==")
|
|
456
|
+
pkg = os.path.dirname(os.path.abspath(__file__))
|
|
457
|
+
total = 0
|
|
458
|
+
for fn, (want, label) in _CALLSITES.items():
|
|
459
|
+
with open(os.path.join(pkg, fn), encoding="utf-8") as fh:
|
|
460
|
+
lines = [ln for ln in fh.read().splitlines()
|
|
461
|
+
if "is_protected(" in ln
|
|
462
|
+
and not ln.strip().startswith("#")
|
|
463
|
+
and "def is_protected" not in ln]
|
|
464
|
+
total += len(lines)
|
|
465
|
+
ok(len(lines) == want,
|
|
466
|
+
"F1 %s 的 is_protected( 调用点 == %d(%s)" % (fn, want, label),
|
|
467
|
+
lines)
|
|
468
|
+
ok(total == 7,
|
|
469
|
+
"F1+ 全部非测试调用点恰 7 处(枚举完备性——新增调用方须同步本表)",
|
|
470
|
+
total)
|
|
471
|
+
with _cg("a_heur") as cg:
|
|
472
|
+
row_a = (protect.is_protected(cg, "a_heur"),
|
|
473
|
+
_guard_line(protect.guard_forget, cg, "a_heur"),
|
|
474
|
+
_guard_line(protect.guard_move, cg, "a_heur", "contextual"),
|
|
475
|
+
_guard_line(protect.guard_overwrite, cg, "a_heur"),
|
|
476
|
+
mcp_server._protect_call(cg, {"action": "check",
|
|
477
|
+
"node_id": "a_heur"}).get(
|
|
478
|
+
"protected"),
|
|
479
|
+
protect.stats(cg)["auto_by_importance"])
|
|
480
|
+
ok(row_a == ((False, ""), None, None, None, False, 0),
|
|
481
|
+
"F2 a_heur 逐类读数行:is_protected/遗忘/搬迁/覆写/check/auto "
|
|
482
|
+
"== 不保护·放·放·放·false·0(改前 True·拦·拦·放·true·1)", row_a)
|
|
483
|
+
with _cg("b_legacy") as cg:
|
|
484
|
+
row_b = (protect.is_protected(cg, "b_legacy"),
|
|
485
|
+
_guard_line(protect.guard_forget, cg, "b_legacy"),
|
|
486
|
+
mcp_server._protect_call(cg, {"action": "check",
|
|
487
|
+
"node_id": "b_legacy"}).get(
|
|
488
|
+
"protected"),
|
|
489
|
+
protect.stats(cg)["auto_by_importance"])
|
|
490
|
+
ok(row_b == ((True, "重要性保护:importance=0.85≥0.7"),
|
|
491
|
+
_BEFORE["b_legacy"]["guard_forget"], True, 1),
|
|
492
|
+
"F2 b_legacy 逐类读数行:存量零回归(逐字同改前)", row_b)
|
|
493
|
+
with _cg("c_hint") as cg:
|
|
494
|
+
row_c = (protect.is_protected(cg, "c_hint"),
|
|
495
|
+
protect.is_immutable(cg, "c_hint"),
|
|
496
|
+
protect.stats(cg)["protected_count"])
|
|
497
|
+
ok(row_c == ((True, "importance=0.90≥0.7"), (False, ""), 1),
|
|
498
|
+
"F2 c_hint 逐类读数行:位分支/immutable 零变化", row_c)
|
|
499
|
+
with _cg("d_self") as cg:
|
|
500
|
+
row_d = (protect.is_protected(cg, "d_self"),
|
|
501
|
+
protect.is_immutable(cg, "d_self"))
|
|
502
|
+
ok(row_d == ((True, "层保护:self(不可遗忘层)"),
|
|
503
|
+
(True, "层保护:self(不可篡改层)")),
|
|
504
|
+
"F2 d_self 逐类读数行:层保护先行(self_state:865 的 protection_missing"
|
|
505
|
+
" 判据同 is_protected ⇒ 恒不报,读数不变)", row_d)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
_GROUPS = (g_f0, g_a, g_b, g_c, g_d, g_e, g_f)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def _run_groups():
|
|
512
|
+
"""跑全部断言组(静默),返回失败数——供变异自证复用。"""
|
|
513
|
+
_PASS.clear()
|
|
514
|
+
_FAIL.clear()
|
|
515
|
+
for g in _GROUPS:
|
|
516
|
+
g()
|
|
517
|
+
return len(_FAIL)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
# ---------------------------------------------------------------- 定点变异自证
|
|
521
|
+
# 表内每项 = (说明, 锚点原文, 替换文, 预期红项数)。锚点必须**逐字**出现在当前
|
|
522
|
+
# is_protected 函数源码里;漂移即 ANCHOR-MISS(fail-closed,exit 2)。
|
|
523
|
+
# 契约 G:m1 把 heuristic 也认保护 → A 红;m2 把缺省键也判不认 → B 红;
|
|
524
|
+
# m3 按分整支删除 → A/B 同红(A 经 A6 同分缺省对照)。
|
|
525
|
+
_SRC_MUTATIONS = (
|
|
526
|
+
# 契约 G①:启发式也认保护(闸加宽一态)。红 11(实测 2026-10-02):
|
|
527
|
+
# A1/A2/A3/A5/A7/A8(启发式读数面 6)+ E1/E2/E3(a_heur 行三格变列锚)
|
|
528
|
+
# + E7(stats 聚合)+ F2 a_heur 行。存量/对照/位面全不红——闸只加宽
|
|
529
|
+
# heuristic 一态,恰证语义按来源分叉。
|
|
530
|
+
("heuristic 也认保护(m1)",
|
|
531
|
+
'(_src is None or _src == "hint")',
|
|
532
|
+
'(_src is None or _src == "hint" or _src == "heuristic")',
|
|
533
|
+
11),
|
|
534
|
+
# 契约 G②:缺省键也判不认(「缺省即不认」被禁的缺陷形态)。红 10
|
|
535
|
+
# (实测 2026-10-02):A6(同分缺省对照)+ B1/B2/B3/B5/B6(存量零回归
|
|
536
|
+
# 锚 5——B4 走 guard_overwrite 放行不受影响)+ E4/E6(b_legacy 行两格,
|
|
537
|
+
# E5 走 guard_overwrite 不红)+ E7(stats 聚合)+ F2 b_legacy 行。
|
|
538
|
+
("缺省键也判不认(m2,禁用缺陷形态)",
|
|
539
|
+
'(_src is None or _src == "hint")',
|
|
540
|
+
'(_src == "hint")',
|
|
541
|
+
10),
|
|
542
|
+
# 契约 G③:按分分支整支删除。红 10(实测同 m2 构成):A6 + B1/B2/B3/
|
|
543
|
+
# B5/B6 + E4/E6 + E7 + F2 b_legacy 行——A/B 同红(A 经 A6 对照;
|
|
544
|
+
# a_heur 行不红,因启发式改后本就 False,整支删除不改变其读数)。
|
|
545
|
+
("按分整支删除(m3)",
|
|
546
|
+
' _src = fm.get("importance_source")\n'
|
|
547
|
+
' if imp >= AUTO_PROTECT_IMPORTANCE and (_src is None or _src == "hint"):\n'
|
|
548
|
+
' return True, f"重要性保护:importance={imp:.2f}≥{AUTO_PROTECT_IMPORTANCE}"',
|
|
549
|
+
' pass',
|
|
550
|
+
10),
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
# 静态锚点(默认模式也自检,fail-closed):
|
|
554
|
+
# · 改前缺陷形态(无来源闸的按分分支)**不得**回归出现在 is_protected 源码里
|
|
555
|
+
# · 本批新判据的锚点必须在位
|
|
556
|
+
_ANCHORS_BANNED = (
|
|
557
|
+
("is_protected", 'if imp >= AUTO_PROTECT_IMPORTANCE:\n return True'),
|
|
558
|
+
)
|
|
559
|
+
_ANCHORS_REQUIRED = (
|
|
560
|
+
("is_protected", '_src = fm.get("importance_source")'),
|
|
561
|
+
("is_protected", 'and (_src is None or _src == "hint"):'),
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def _fn_src(target):
|
|
566
|
+
if target == "is_protected":
|
|
567
|
+
return inspect.getsource(protect.is_protected)
|
|
568
|
+
raise KeyError(target)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def _anchor_check():
|
|
572
|
+
"""返回 ANCHOR-MISS 说明列表(空 = 全部在位)。"""
|
|
573
|
+
bad = []
|
|
574
|
+
for _name, old, _new, _n in _SRC_MUTATIONS:
|
|
575
|
+
if old not in _fn_src("is_protected"):
|
|
576
|
+
bad.append("变异锚点缺失:%r @is_protected" % old)
|
|
577
|
+
for target, s in _ANCHORS_BANNED:
|
|
578
|
+
if s in _fn_src(target):
|
|
579
|
+
bad.append("缺陷形态回归:%s 可执行源码里仍有 %r" % (target, s))
|
|
580
|
+
for target, s in _ANCHORS_REQUIRED:
|
|
581
|
+
if s not in _fn_src(target):
|
|
582
|
+
bad.append("新判据锚点缺失:%s 找不到 %r" % (target, s))
|
|
583
|
+
return bad
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
@contextlib.contextmanager
|
|
587
|
+
def _patched_ctx(old, new):
|
|
588
|
+
"""把 protect.is_protected 按字面替换变异后安装/还原(不落盘不改源文件)。
|
|
589
|
+
|
|
590
|
+
guard_forget / guard_move / stats 经 protect 模块 __dict__ 解析
|
|
591
|
+
is_protected,mcp_server/scrub/self_state 经 `from . import protect`
|
|
592
|
+
属性查找——setattr 后全部出口同闸生效。
|
|
593
|
+
"""
|
|
594
|
+
src = _fn_src("is_protected")
|
|
595
|
+
mutated = src.replace(old, new)
|
|
596
|
+
ns = dict(vars(protect))
|
|
597
|
+
exec(compile(mutated, "i50e_mut.py", "exec"), ns)
|
|
598
|
+
live = protect.is_protected
|
|
599
|
+
protect.is_protected = ns["is_protected"]
|
|
600
|
+
try:
|
|
601
|
+
yield
|
|
602
|
+
finally:
|
|
603
|
+
protect.is_protected = live
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def _mutate_mode():
|
|
607
|
+
bad = []
|
|
608
|
+
anchor_bad = _anchor_check()
|
|
609
|
+
if anchor_bad:
|
|
610
|
+
for b in anchor_bad:
|
|
611
|
+
print(" ANCHOR-MISS " + b)
|
|
612
|
+
print("\n锚点自检:FAIL(fail-closed,exit 2)")
|
|
613
|
+
return 2
|
|
614
|
+
with contextlib.redirect_stdout(io.StringIO()):
|
|
615
|
+
clean = _run_groups()
|
|
616
|
+
print(" 未变异基线:红项=%d%s"
|
|
617
|
+
% (clean, "" if clean == 0 else " ← 基线即红,变异核验无意义"))
|
|
618
|
+
if clean:
|
|
619
|
+
bad.append("未变异基线即失败")
|
|
620
|
+
for name, old, new, expect in _SRC_MUTATIONS:
|
|
621
|
+
with _patched_ctx(old, new), \
|
|
622
|
+
contextlib.redirect_stdout(io.StringIO()):
|
|
623
|
+
fails = _run_groups()
|
|
624
|
+
verdict = ("命中预期" if fails == expect
|
|
625
|
+
else "**红项数不符(预期 %d)**" % expect)
|
|
626
|
+
print(" 变异「%s」→ 红项=%d %s" % (name, fails, verdict))
|
|
627
|
+
if fails != expect:
|
|
628
|
+
bad.append(name)
|
|
629
|
+
print("\n定点变异自证:%s"
|
|
630
|
+
% ("PASS(每处判据都有变异钉死,且红项数逐处吻合)" if not bad
|
|
631
|
+
else "FAIL —— " + "、".join(bad)))
|
|
632
|
+
return 0 if not bad else 1
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
def main() -> int:
|
|
636
|
+
src = os.path.basename(os.path.abspath(__file__))
|
|
637
|
+
if "--mutate" in sys.argv:
|
|
638
|
+
print("!! 定点变异模式:逐个变异判据,套件应转红且红项数吻合\n")
|
|
639
|
+
return _mutate_mode()
|
|
640
|
+
anchor_bad = _anchor_check()
|
|
641
|
+
if anchor_bad:
|
|
642
|
+
for b in anchor_bad:
|
|
643
|
+
print(" ANCHOR-MISS " + b)
|
|
644
|
+
print("\n锚点自检:FAIL(fail-closed,exit 2)——实现改了请同步变异表")
|
|
645
|
+
return 2
|
|
646
|
+
print("锚点自检:PASS(%s;不以 git HEAD 为基线源)" % src)
|
|
647
|
+
for g in _GROUPS:
|
|
648
|
+
g()
|
|
649
|
+
print("\nissue50-e 守卫:%d 通过,%d 失败" % (len(_PASS), len(_FAIL)))
|
|
650
|
+
if _FAIL:
|
|
651
|
+
print("失败项:" + "、".join(_FAIL))
|
|
652
|
+
return 0 if not _FAIL else 1
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
if __name__ == "__main__":
|
|
656
|
+
sys.exit(main())
|