@furongjun1999/dsh-memory 0.7.2 → 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 +56 -47
- package/codebuddy/CODEBUDDY.md +6 -6
- 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/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 +6 -5
- 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/bench_e2e_judge.py +4 -2
- package/md_cg/branches.py +29 -3
- 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 -4822
- package/md_cg/mdcos.py +31 -12
- package/md_cg/nodefile.py +77 -13
- package/md_cg/readcache.py +29 -4
- 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_datapath_device_name.py +62 -3
- package/md_cg/test_generation_guard.py +5 -2
- package/md_cg/test_ghostref.py +182 -0
- package/md_cg/test_interop.py +1 -1
- package/md_cg/test_issue52_scan_condition_first.py +1 -1
- 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 +6 -6
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""L5 证据审计面:只读组装器(路线 C)——「现值+出处链+两存/备择+盲区+退役史」。
|
|
3
|
+
|
|
4
|
+
多主体世界模型对齐 v0.1 §4 路线 C:「给节点 id 输出『现值+出处链+两存/备择+
|
|
5
|
+
盲区+退役史』。**纯读、零写路径改动**」。命名约束(设计稿复核修订):**不得
|
|
6
|
+
复用 `view` 参数名**(已被 roleviews 占用,mdcos._candidates 的 view 面)。
|
|
7
|
+
|
|
8
|
+
五面数据源(全部只读):
|
|
9
|
+
· 现值 —— `cg.get` 的 fm + 正文(层/生命态/验证态/时效/A1 检验强度/
|
|
10
|
+
A2 幽灵标记/归属面);
|
|
11
|
+
· 出处链 —— fm.derived_from(血缘)+ fm.edges(它引用谁)+ **反查**
|
|
12
|
+
索引条目 edges(谁引用它,内存扫描免读盘)+ provenance 台账
|
|
13
|
+
`_link.jsonl` 相关行;
|
|
14
|
+
· 两存/备择 —— `cg.check_consistency(..., auto_flywheel=False, exclude=<自身>)`
|
|
15
|
+
的**只读复用**(与写入链同一判据):同条件空间分歧/对照数/
|
|
16
|
+
截断如实透出(「两存之为默认、冲突一等」的可见化);
|
|
17
|
+
· 盲区 —— negative/positive evidence 计数、非适用条件、A2 幽灵标记、
|
|
18
|
+
trust.validity 判定(未生效/已过期/未知)、verification_state;
|
|
19
|
+
· 退役史 —— fm.state_history / fm.verification_history(lifecycle/trust
|
|
20
|
+
的既有 HISTORY_FIELD 快照)+ `evolution.history` 台账。
|
|
21
|
+
|
|
22
|
+
成本如实:备择面复用一致性检查,大库上有候选截断(截断计数如实带出,hint
|
|
23
|
+
与写入面同款);本组装器**不落盘、不改任何状态**。超出 cap 的面截断并计数。
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import os
|
|
28
|
+
|
|
29
|
+
__all__ = ["build"]
|
|
30
|
+
|
|
31
|
+
#: 各面截断上限(超限计数并如实带出 truncated)。
|
|
32
|
+
DEFAULT_CAP = 8
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _readable_guard(cg):
|
|
36
|
+
"""读隔离谓词(与 linkref.known_ids 同口径):不可调用时全放行。"""
|
|
37
|
+
fn = getattr(cg, "_readable", None)
|
|
38
|
+
return fn if callable(fn) else (lambda _e: True)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _referenced_by(cg, node_id, cap):
|
|
42
|
+
"""反查:谁引用了本节点(索引条目 edges 的内存扫描;免读盘)。
|
|
43
|
+
|
|
44
|
+
**读隔离**(2026-10-05 复核 R3):扫描按 `cg._readable` 过滤——不可读
|
|
45
|
+
节点的 id 与其引用关系不得经本面泄漏(与 linkref.known_ids /
|
|
46
|
+
evolution.entries 同口径:「不可见即不存在」)。
|
|
47
|
+
"""
|
|
48
|
+
# H-4 纪律(test_h4_sustain_snapshot G1a):从 cg.index 取回的 nodes
|
|
49
|
+
# 须先取**快照**再迭代——并发写者 flush 时裸迭代会撞「字典变更」。
|
|
50
|
+
_live = (getattr(cg, "index", None) or {}).get("nodes") or {}
|
|
51
|
+
nodes = dict(_live)
|
|
52
|
+
guard = _readable_guard(cg)
|
|
53
|
+
out, total = [], 0
|
|
54
|
+
for nid, e in nodes.items():
|
|
55
|
+
if nid == node_id:
|
|
56
|
+
continue
|
|
57
|
+
try:
|
|
58
|
+
if not guard(e):
|
|
59
|
+
continue
|
|
60
|
+
except Exception: # noqa: BLE001 —— fail-closed
|
|
61
|
+
continue
|
|
62
|
+
for ed in (e.get("edges") or []):
|
|
63
|
+
if str(ed.get("target")) == node_id:
|
|
64
|
+
total += 1
|
|
65
|
+
if len(out) < cap:
|
|
66
|
+
out.append({"from": nid, "relation": ed.get("relation_type"),
|
|
67
|
+
"confidence": ed.get("confidence"),
|
|
68
|
+
"source": ed.get("source")})
|
|
69
|
+
break
|
|
70
|
+
return {"items": out, "total": total, "truncated": total > len(out)}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _link_rows(cg, node_id, cap):
|
|
74
|
+
"""provenance 台账(`_link.jsonl`)里与本节点相关的行。
|
|
75
|
+
|
|
76
|
+
**schema 对齐 provenance.make_edge**(2026-10-05 复核 R1 修):真字段是
|
|
77
|
+
`child/parent/rel/t/actor`(原实现按 `target/source/relation/at` 匹配
|
|
78
|
+
⇒ 恒读空,整面死)。「与本节点相关」= child==本节点(它派生自谁)或
|
|
79
|
+
parent==本节点(谁派生自它)。
|
|
80
|
+
|
|
81
|
+
**行级读隔离**(2026-10-05 复验 R4 修):行是否可见取决于**另一端**——
|
|
82
|
+
「不可见即不存在」的口径下,verify 身份不得经本面看到不可读 secret
|
|
83
|
+
节点的 id 与其派生关系(复验实证:修复前与 designer 视角逐位相同)。
|
|
84
|
+
另一端条目不在索引或不可读 ⇒ **整行不出**(fail-closed,与
|
|
85
|
+
`_referenced_by` 的 guard 同口径;自身端必可读——本函数只在本节点
|
|
86
|
+
已可读[build 的 cg.get 成功]之后被调用)。
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
from . import provenance
|
|
90
|
+
path = provenance.ledger_file(getattr(cg, "root", None))
|
|
91
|
+
except Exception: # noqa: BLE001
|
|
92
|
+
return {"items": [], "total": 0, "truncated": False}
|
|
93
|
+
if not path or not os.path.exists(path):
|
|
94
|
+
return {"items": [], "total": 0, "truncated": False}
|
|
95
|
+
_live = (getattr(cg, "index", None) or {}).get("nodes") or {}
|
|
96
|
+
nodes = dict(_live) # H-4 快照纪律(迭代前取快照)
|
|
97
|
+
guard = _readable_guard(cg)
|
|
98
|
+
out, total = [], 0
|
|
99
|
+
try:
|
|
100
|
+
with open(path, encoding="utf-8", errors="replace") as f:
|
|
101
|
+
for line in f:
|
|
102
|
+
line = line.strip()
|
|
103
|
+
if not line:
|
|
104
|
+
continue
|
|
105
|
+
try:
|
|
106
|
+
import json
|
|
107
|
+
rec = json.loads(line)
|
|
108
|
+
except Exception: # noqa: BLE001
|
|
109
|
+
continue
|
|
110
|
+
if not isinstance(rec, dict):
|
|
111
|
+
continue
|
|
112
|
+
c, p = str(rec.get("child")), str(rec.get("parent"))
|
|
113
|
+
if c == node_id:
|
|
114
|
+
other = p
|
|
115
|
+
elif p == node_id:
|
|
116
|
+
other = c
|
|
117
|
+
else:
|
|
118
|
+
continue
|
|
119
|
+
e_other = nodes.get(other)
|
|
120
|
+
if e_other is None:
|
|
121
|
+
continue # 另一端不在索引(含已删)→ 不出
|
|
122
|
+
try:
|
|
123
|
+
if not guard(e_other):
|
|
124
|
+
continue # 另一端不可读 → 整行不出
|
|
125
|
+
except Exception: # noqa: BLE001 —— fail-closed
|
|
126
|
+
continue
|
|
127
|
+
total += 1
|
|
128
|
+
if len(out) < cap:
|
|
129
|
+
out.append({k: rec.get(k) for k in
|
|
130
|
+
("child", "parent", "rel", "t", "actor", "note")})
|
|
131
|
+
except Exception: # noqa: BLE001 —— 只读面不抛
|
|
132
|
+
pass
|
|
133
|
+
return {"items": out, "total": total, "truncated": total > len(out)}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def build(cg, node_id, cap=DEFAULT_CAP) -> dict:
|
|
137
|
+
"""组装五面只读视图。node_id 不存在 ⇒ {"ok": False, "error": "node_not_found"}。
|
|
138
|
+
|
|
139
|
+
cap 防御(2026-10-05 复核 Y2):非数/None 回落缺省、钳 [1, 64]——
|
|
140
|
+
直调面与 MCP 面同语义(MCP 面的钳制在分发层,直调 API 不得裸抛 TypeError)。
|
|
141
|
+
"""
|
|
142
|
+
try:
|
|
143
|
+
cap = int(cap)
|
|
144
|
+
except (TypeError, ValueError):
|
|
145
|
+
cap = DEFAULT_CAP
|
|
146
|
+
cap = max(1, min(64, cap))
|
|
147
|
+
nid = str(node_id or "").strip()
|
|
148
|
+
if not nid:
|
|
149
|
+
return {"ok": False, "error": "node_not_found", "node_id": node_id}
|
|
150
|
+
rec = cg.get(nid)
|
|
151
|
+
if not rec:
|
|
152
|
+
return {"ok": False, "error": "node_not_found", "node_id": nid}
|
|
153
|
+
fm = rec.get("frontmatter") or {}
|
|
154
|
+
content = rec.get("content") or ""
|
|
155
|
+
from . import evolution, lifecycle, nodefile, trust
|
|
156
|
+
|
|
157
|
+
# ---- 面一:现值 ----
|
|
158
|
+
try:
|
|
159
|
+
_validity = trust.validity(fm)
|
|
160
|
+
except Exception as exc: # noqa: BLE001
|
|
161
|
+
_validity = "%s: %s" % (type(exc).__name__, exc)
|
|
162
|
+
present = {
|
|
163
|
+
"layer": fm.get("layer"),
|
|
164
|
+
"lifecycle_state": lifecycle.state_of(fm),
|
|
165
|
+
"verification_state": trust.state_of(fm),
|
|
166
|
+
"validity": _validity,
|
|
167
|
+
"importance": fm.get("importance"),
|
|
168
|
+
"sensitivity": fm.get("sensitivity"),
|
|
169
|
+
"created_at": fm.get("created_at"),
|
|
170
|
+
"tags": fm.get("tags"),
|
|
171
|
+
"verification_basis": fm.get("verification_basis"),
|
|
172
|
+
nodefile.CHECK_STRENGTH_FIELD: fm.get(nodefile.CHECK_STRENGTH_FIELD),
|
|
173
|
+
nodefile.UNCERTAIN_REFS_FIELD: fm.get(nodefile.UNCERTAIN_REFS_FIELD),
|
|
174
|
+
"writer": fm.get("writer"), "harness": fm.get("harness"),
|
|
175
|
+
"session": fm.get("session"),
|
|
176
|
+
"content_chars": len(content),
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# ---- 面二:出处链 ----
|
|
180
|
+
_edges = list(fm.get("edges") or [])
|
|
181
|
+
sources = {
|
|
182
|
+
"derived_from": fm.get("derived_from"),
|
|
183
|
+
"derived_relation": fm.get("derived_relation"),
|
|
184
|
+
"refers_to": {"items": _edges[:cap], "total": len(_edges),
|
|
185
|
+
"truncated": len(_edges) > cap},
|
|
186
|
+
"referenced_by": _referenced_by(cg, nid, cap),
|
|
187
|
+
"link_ledger": _link_rows(cg, nid, cap),
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# ---- 面三:两存/备择(一致性只读复用;不建单、不落盘)----
|
|
191
|
+
alternatives = {"comparable": None, "verdict": None, "conflicts": [],
|
|
192
|
+
"missing": [], "truncated": None, "scanned": None}
|
|
193
|
+
try:
|
|
194
|
+
# log_write=False(复核 R2 修):只读消费面——判定照常、台账不写
|
|
195
|
+
#(consistency.log 原为无条件追加,审计会变成隐式写)。
|
|
196
|
+
cvd = cg.check_consistency(
|
|
197
|
+
content, layer=fm.get("layer") or "knowledge",
|
|
198
|
+
condition_space=fm.get("condition_space"),
|
|
199
|
+
non_applicable_conditions=fm.get("non_applicable_conditions"),
|
|
200
|
+
tags=fm.get("tags"), exclude=nid, auto_flywheel=False,
|
|
201
|
+
log_write=False)
|
|
202
|
+
if isinstance(cvd, dict):
|
|
203
|
+
cf = list(cvd.get("conflicts") or [])
|
|
204
|
+
alternatives = {
|
|
205
|
+
"verdict": cvd.get("verdict"), "reason": cvd.get("reason"),
|
|
206
|
+
"conflict_strength": cvd.get("conflict_strength"),
|
|
207
|
+
"comparable": cvd.get("comparable"),
|
|
208
|
+
"scanned": cvd.get("scanned"), "truncated": cvd.get("truncated"),
|
|
209
|
+
"conflicts": cf[:cap],
|
|
210
|
+
"conflicts_total": len(cf), "conflicts_truncated": len(cf) > cap,
|
|
211
|
+
"missing": list(cvd.get("missing") or [])[:cap],
|
|
212
|
+
"hint": cvd.get("hint"),
|
|
213
|
+
}
|
|
214
|
+
except Exception as exc: # noqa: BLE001 —— 只读面不抛
|
|
215
|
+
alternatives["error"] = "%s: %s" % (type(exc).__name__, exc)
|
|
216
|
+
|
|
217
|
+
# ---- 面四:盲区 ----
|
|
218
|
+
blindspots = {
|
|
219
|
+
"negative_evidence": fm.get("negative_evidence", 0),
|
|
220
|
+
"positive_evidence": fm.get("positive_evidence", 0),
|
|
221
|
+
"non_applicable_conditions": fm.get("non_applicable_conditions"),
|
|
222
|
+
nodefile.UNCERTAIN_REFS_FIELD: fm.get(nodefile.UNCERTAIN_REFS_FIELD),
|
|
223
|
+
"verification_state": trust.state_of(fm),
|
|
224
|
+
"validity": _validity,
|
|
225
|
+
"note": ("未决不进二值:DEFER/unresolved 痕迹在『两存/备择』与 "
|
|
226
|
+
"negative_evidence 面;本面只呈现,不下判断。"),
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
# ---- 面五:退役史 ----
|
|
230
|
+
_sh = list(fm.get(lifecycle.HISTORY_FIELD) or [])
|
|
231
|
+
_vh = list(fm.get(trust.HISTORY_FIELD) or [])
|
|
232
|
+
_ev = []
|
|
233
|
+
try:
|
|
234
|
+
_ev = list((evolution.history(cg, nid, limit=cap) or {}).get("entries") or [])
|
|
235
|
+
except Exception: # noqa: BLE001
|
|
236
|
+
pass
|
|
237
|
+
history = {
|
|
238
|
+
lifecycle.HISTORY_FIELD: _sh[-cap:],
|
|
239
|
+
trust.HISTORY_FIELD: _vh[-cap:],
|
|
240
|
+
"evolution": _ev[:cap],
|
|
241
|
+
"totals": {lifecycle.HISTORY_FIELD: len(_sh),
|
|
242
|
+
trust.HISTORY_FIELD: len(_vh), "evolution_scanned": len(_ev)},
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {"ok": True, "node_id": nid, "cap": cap,
|
|
246
|
+
"present": present, "sources": sources,
|
|
247
|
+
"alternatives": alternatives, "blindspots": blindspots,
|
|
248
|
+
"history": history}
|
package/md_cg/bench_e2e_judge.py
CHANGED
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
python -X utf8 -m md_cg.bench_e2e_judge --quick # 冒烟 20题×level 0,1×无LLM
|
|
41
41
|
python -X utf8 -m md_cg.bench_e2e_judge # 全量 120题×4级×3臂+LLM两轮
|
|
42
42
|
python -X utf8 -m md_cg.bench_e2e_judge --skip-llm # 只跑确定性两臂
|
|
43
|
-
环境:DEEPSEEK_API_KEY(LLM 臂);数据落 --data-root(默认
|
|
43
|
+
环境:DEEPSEEK_API_KEY(LLM 臂);数据落 --data-root(默认 <仓根父目录>/test/e2e_judge,
|
|
44
|
+
可用 MDCG_E2E_JUDGE_ROOT 覆盖)
|
|
44
45
|
"""
|
|
45
46
|
from __future__ import annotations
|
|
46
47
|
|
|
@@ -64,7 +65,8 @@ from md_cg.mdcos import MdCGOS # noqa: E402
|
|
|
64
65
|
from md_cg import nodefile # noqa: E402
|
|
65
66
|
|
|
66
67
|
DATA_SRC = os.path.join(HERE, "data", "benchmarks", "locomo-zh-500")
|
|
67
|
-
DEFAULT_ROOT =
|
|
68
|
+
DEFAULT_ROOT = os.environ.get("MDCG_E2E_JUDGE_ROOT") or os.path.join(
|
|
69
|
+
os.path.dirname(HERE), "test", "e2e_judge")
|
|
68
70
|
FUSION_KW = dict(paths=("lexical", "bucket", "entity", "graph", "fuzzy"),
|
|
69
71
|
fusion="sum")
|
|
70
72
|
LEVELS_DEFAULT = (0, 1, 2, 4)
|
package/md_cg/branches.py
CHANGED
|
@@ -111,6 +111,12 @@ def fork(cg, node_ids, branch_id=None, note=None) -> dict:
|
|
|
111
111
|
condition_space=fm.get("condition_space"),
|
|
112
112
|
importance=float(fm.get("importance", 0.5)),
|
|
113
113
|
verification_basis=fm.get("verification_basis") or "test",
|
|
114
|
+
# A1(2026-10-05,复核补):断言质量字段随副本携带(与
|
|
115
|
+
# verification_basis 同族;缺省 None 时 add 不落键,零行为变化)。
|
|
116
|
+
check_strength=fm.get("check_strength"),
|
|
117
|
+
# A2(2026-10-05):幽灵引用标记随副本携带(正文属性——副本
|
|
118
|
+
# 正文与主支一致则标记有效;缺省 None 不落键)。
|
|
119
|
+
uncertain_refs=fm.get("uncertain_refs"),
|
|
114
120
|
non_applicable_conditions=fm.get("non_applicable_conditions"),
|
|
115
121
|
consistency=False,
|
|
116
122
|
derived_from=[nid], relation="split_from",
|
|
@@ -156,8 +162,16 @@ def rewrite(cg, node_id, content, tags=None, importance=None,
|
|
|
156
162
|
if isinstance(content, dict) or content is None:
|
|
157
163
|
return {"ok": False, "error": "content 须为字符串"}
|
|
158
164
|
for k in ("branch_id", "branched_from", "branch_note", "derived_from",
|
|
159
|
-
"relation", "merged_from_branch", "override", "consistency"
|
|
160
|
-
|
|
165
|
+
"relation", "merged_from_branch", "override", "consistency",
|
|
166
|
+
"uncertain_refs"):
|
|
167
|
+
extra.pop(k, None) # 归属/血缘/写法闸/检测产物不可经 extra 篡改
|
|
168
|
+
# A2(2026-10-05,复核 yellow-1 修):标记按**新正文**重算——本函数是
|
|
169
|
+
# 「改正文的唯一正路」,携带旧标记会与正文脱节(干净正文带陈旧标记 /
|
|
170
|
+
# 幽灵正文漏标,双向都错);与 mdcg.add「正文属性、按当次检测重算」
|
|
171
|
+
# 的声明同口径。
|
|
172
|
+
from . import ghostref as _ghostref
|
|
173
|
+
_ur = _ghostref.find_ghost_phrases(
|
|
174
|
+
str(content or ""), known=set((cg.index.get("nodes") or {}).keys()))
|
|
161
175
|
cg.add(nid, str(content or ""),
|
|
162
176
|
layer=fm.get("layer") or e.get("layer") or "knowledge",
|
|
163
177
|
tags=list(tags if tags is not None else (fm.get("tags") or [])),
|
|
@@ -165,6 +179,10 @@ def rewrite(cg, node_id, content, tags=None, importance=None,
|
|
|
165
179
|
else (fm.get("importance") or 0.5)),
|
|
166
180
|
condition_space=fm.get("condition_space"),
|
|
167
181
|
verification_basis=fm.get("verification_basis") or "test",
|
|
182
|
+
# A1(2026-10-05,复核补):断言质量字段随重写携带(同上)。
|
|
183
|
+
check_strength=fm.get("check_strength"),
|
|
184
|
+
# A2(2026-10-05):幽灵引用标记按新正文重算(见上)。
|
|
185
|
+
uncertain_refs=_ur or None,
|
|
168
186
|
non_applicable_conditions=fm.get("non_applicable_conditions"),
|
|
169
187
|
override=True, consistency=False,
|
|
170
188
|
derived_from=list(fm.get("derived_from") or []),
|
|
@@ -203,13 +221,21 @@ def merge(cg, branch_id, reason=None) -> dict:
|
|
|
203
221
|
chain = list(old_fm.get("derived_from") or [])
|
|
204
222
|
if nid not in chain:
|
|
205
223
|
chain.append(nid)
|
|
206
|
-
|
|
224
|
+
# A2(2026-10-05,复核 yellow-2① 修):主支正文 = 分支正文 ⇒ 标记按
|
|
225
|
+
# 该正文重算(此前不携带 = 幽灵正文回写后无标记,方向与「宁标勿漏」
|
|
226
|
+
# 相反;fork/rewrite 已接线,此处补齐同族)。
|
|
227
|
+
from . import ghostref as _ghostref
|
|
228
|
+
_mcontent = (src or {}).get("content") or ""
|
|
229
|
+
_mur = _ghostref.find_ghost_phrases(
|
|
230
|
+
_mcontent, known=set((cg.index.get("nodes") or {}).keys()))
|
|
231
|
+
cg.add(orig, _mcontent,
|
|
207
232
|
layer=old_fm.get("layer") or e.get("layer") or "knowledge",
|
|
208
233
|
tags=list(old_fm.get("tags") or e.get("tags") or []),
|
|
209
234
|
condition_space=src_fm.get("condition_space"),
|
|
210
235
|
importance=float(old_fm.get("importance", 0.5)),
|
|
211
236
|
verification_basis=old_fm.get("verification_basis") or "test",
|
|
212
237
|
non_applicable_conditions=old_fm.get("non_applicable_conditions"),
|
|
238
|
+
uncertain_refs=_mur or None,
|
|
213
239
|
override=True, consistency=False,
|
|
214
240
|
derived_from=chain, relation="merged_from",
|
|
215
241
|
merged_from_branch=bid, actor="branch_merge")
|
package/md_cg/consistency.py
CHANGED
|
@@ -51,6 +51,7 @@ import os
|
|
|
51
51
|
import time
|
|
52
52
|
|
|
53
53
|
from .mdcg import expand_query_terms_weighted
|
|
54
|
+
from . import nodefile
|
|
54
55
|
|
|
55
56
|
# --------------------------------------------------------------------------
|
|
56
57
|
# 常量(全部可审计、可调)
|
|
@@ -250,21 +251,28 @@ def _ban_hit(content, neg_texts):
|
|
|
250
251
|
return False
|
|
251
252
|
|
|
252
253
|
|
|
253
|
-
#
|
|
254
|
+
# 生效条件:委托 nodefile._strip_ccg_segments(content, nodefile.CCG_MARKS)(剥除算法单点)——命中 CCG 要素标题行(判据单点 nodefile._ccg_heading_rest,`^#\s*<要素>` 冒号可有可无)即整段剥除:行内带值(`# 生效条件:v`/前缀式)只剥该行,裸标题(`# 不适用条件`)连同其后首个非空、非标题行(该字段的值行,取值口径同 nodefile.ccg_field_value)一并剥除;其余原行以换行连接返回;content 假值按空串返回 "";
|
|
254
255
|
def _body_text(content):
|
|
255
|
-
"""去掉 CCG
|
|
256
|
+
"""去掉 CCG 声明**整段字段**后的正文。
|
|
256
257
|
|
|
257
258
|
自否定看的是「正文/生效条件是否与不适用条件矛盾」,不能把节点自己声明的
|
|
258
259
|
`# 不适用条件:X` 当成 X 出现在正文里——否则**每个**声明了不适用条件的
|
|
259
260
|
正常节点都会被误判为自相矛盾(真实 CCG 条目普遍带该字段)。
|
|
261
|
+
|
|
262
|
+
N238:判据复用**单点** `nodefile._ccg_heading_rest`(与写入闸门
|
|
263
|
+
data/policy.json 同一行语义、`mdcos._ccg_field` / `ccg_mark_present` 同源),
|
|
264
|
+
并连同**无冒号形态的值行**一起剥。此前本地判据是「以 # 开头**且含冒号**」,
|
|
265
|
+
裸标题 `# 不适用条件` 与其值行双双残留正文:同内容写成带冒号时
|
|
266
|
+
`_body_text == ''`(不命中),写成无冒号时 body 含禁令短语 ⇒ `_ban_hit`
|
|
267
|
+
恒中 ⇒ self_negation / strength=1.0 ⇒ REJECT(writepipe 下合法节点被拦入
|
|
268
|
+
review_queue 并自动建飞轮工单)。「冒号可有可无」已由
|
|
269
|
+
`nodefile.ccg_mark_present` / `ccg_field_value` 定案(test_ccg_form_parity
|
|
270
|
+
验收 PASS),本处是漏网的分叉。
|
|
271
|
+
|
|
272
|
+
A3 追认:剥除算法本身也已收单点 `nodefile._strip_ccg_segments`(六要素全剥)
|
|
273
|
+
——本处不再自持循环,避免与 `nodefile.positive_body` 各写一份而再次漂移。
|
|
260
274
|
"""
|
|
261
|
-
|
|
262
|
-
for line in (content or "").splitlines():
|
|
263
|
-
s = line.strip()
|
|
264
|
-
if s.startswith("#") and (":" in s or ":" in s):
|
|
265
|
-
continue
|
|
266
|
-
out.append(line)
|
|
267
|
-
return "\n".join(out)
|
|
275
|
+
return nodefile._strip_ccg_segments(content, nodefile.CCG_MARKS)
|
|
268
276
|
|
|
269
277
|
|
|
270
278
|
# 生效条件:text 假值时按空串处理,返回把任意连续空白(半角/全角空格、\t、\r、\n)折叠为单个半角空格并去掉首尾空白后的字符串;纯空白文本返回空串 "";
|
|
@@ -484,16 +492,21 @@ def _recursive_reflect(cg, seeds, tw_pos, tw_neg, max_depth=MAX_DEPTH,
|
|
|
484
492
|
# 主入口:三级决策
|
|
485
493
|
# --------------------------------------------------------------------------
|
|
486
494
|
|
|
487
|
-
# 生效条件:以 cg.index.nodes 为既有节点、content(假值按 "")经 _new_terms 得 pos/neg 并算 tw_pos/tw_neg(non_applicable_conditions 入参先按 mdcos._is_null_condition 剔除空值语义哨兵——⑧ 漏斗单点);选面(cons200;收口轮①加面内例外):快照级预筛剔「tw_pos 与 tw_neg 皆空(新节点未声明可展开条件)时的非纪律候选」,且**剔除永不触及修前扫描面**(过滤后索引序前 limit 内候选一律保留——面内例外;「修后检出 ⊇ 修前」由此成为与「条目 tags 与盘面 fm.tags 同源」无关的结构保证),候选超 limit 时精比面=相关面(_sift_score 序)前 limit ∪ 保底面(过滤后索引序前 limit ∩ 候选),记 scanned(候选数)/kept(精比数)/truncated(scanned>kept)/hint;按循环中 hard(自否定或纪律命中)→ divergences(同条件槽且 concl < CONCLUSION_SAME)→ strength ≥ CLASH_HIGH → strength ≥ CLASH_LOW → comparable==0 且 (pos or neg) → 否则 ACCEPT 的顺序定 verdict;DEFER 且 int(depth)>0 且 emo["bias"] != "approaching" 时调 _recursive_reflect 补 recursion,auto_flywheel 且 verdict == REJECT 时加 unresolved_id,最后 log 并返回 rec;
|
|
495
|
+
# 生效条件:以 cg.index.nodes 为既有节点、content(假值按 "")经 _new_terms 得 pos/neg 并算 tw_pos/tw_neg(non_applicable_conditions 入参先按 mdcos._is_null_condition 剔除空值语义哨兵——⑧ 漏斗单点);选面(cons200;收口轮①加面内例外):快照级预筛剔「tw_pos 与 tw_neg 皆空(新节点未声明可展开条件)时的非纪律候选」,且**剔除永不触及修前扫描面**(过滤后索引序前 limit 内候选一律保留——面内例外;「修后检出 ⊇ 修前」由此成为与「条目 tags 与盘面 fm.tags 同源」无关的结构保证),候选超 limit 时精比面=相关面(_sift_score 序)前 limit ∪ 保底面(过滤后索引序前 limit ∩ 候选),记 scanned(候选数)/kept(精比数)/truncated(scanned>kept)/hint;按循环中 hard(自否定或纪律命中)→ divergences(同条件槽且 concl < CONCLUSION_SAME)→ strength ≥ CLASH_HIGH → strength ≥ CLASH_LOW → comparable==0 且 (pos or neg) → 否则 ACCEPT 的顺序定 verdict;DEFER 且 int(depth)>0 且 emo["bias"] != "approaching" 时调 _recursive_reflect 补 recursion,auto_flywheel 且 verdict == REJECT 时加 unresolved_id,最后 log_write 为真时 log 并返回 rec;N276:精比环的索引代际探针循环外单探一次(环内 cg.get(nid, probe=False);无探活面载体回落 cg.get(nid),行为零变);
|
|
488
496
|
def check(cg, content, layer=None, condition_space=None,
|
|
489
497
|
non_applicable_conditions=None, tags=None, exclude=None,
|
|
490
498
|
limit=MAX_SCAN, depth=MAX_DEPTH, auto_flywheel=False,
|
|
491
|
-
query=None):
|
|
499
|
+
query=None, log_write=True):
|
|
492
500
|
"""节点间自动冲突检测(L0 情绪 → L1 反思 → L2 递归反思)。
|
|
493
501
|
|
|
494
502
|
返回完整判据(可审计):
|
|
495
503
|
verdict / reason / conflict_strength / emotional / conflicts[] /
|
|
496
504
|
recursion{} / missing[] / unresolved_id(若触发飞轮)
|
|
505
|
+
|
|
506
|
+
log_write(路线 C,2026-10-05):缺省 True=原行为(每次判定落
|
|
507
|
+
`_consistency.jsonl` 台账)。**只读消费面**(如 auditview 的证据审计)
|
|
508
|
+
须传 False——判定结果照常返回,但不写台账(复核 R2:审计面承诺
|
|
509
|
+
「纯读、不落盘」,而 log() 无条件追加会把每次审计变成一次写)。
|
|
497
510
|
+ 选面读数 scanned / kept / truncated(超限时另附 hint)
|
|
498
511
|
|
|
499
512
|
verdict:
|
|
@@ -619,9 +632,20 @@ def check(cg, content, layer=None, condition_space=None,
|
|
|
619
632
|
scanned = len(cand) # 预筛后候选数(截断前)
|
|
620
633
|
kept = len(scan_face) # 实际精比数(相关面 ∪ 保底面)
|
|
621
634
|
truncated = scanned > kept
|
|
635
|
+
# N276(第 32 轮性能面):scan_face 精比环内逐节点 cg.get 会各自重探索引
|
|
636
|
+
# 签名(快照 stat + listdir + 逐分片 stat,单点实测 ~38µs;本库 200 次/
|
|
637
|
+
# 调用全同签名重探)——循环外单探一次、环内 probe=False;语义窗口=本批
|
|
638
|
+
# 读期间「他进程写入」不被探知(毫秒级,与读缓存已声明的进程内一致性
|
|
639
|
+
# 边界同款);无探活面的载体(测试桩)回落原 cg.get(nid),行为零变。
|
|
640
|
+
_probe = getattr(cg, "_maybe_reload_index", None)
|
|
641
|
+
if callable(_probe):
|
|
642
|
+
_probe()
|
|
643
|
+
else:
|
|
644
|
+
_probe = None
|
|
622
645
|
seeds = []
|
|
623
646
|
for nid, e in scan_face:
|
|
624
|
-
node = cg.get(nid)
|
|
647
|
+
node = (cg.get(nid, probe=False) if _probe is not None
|
|
648
|
+
else cg.get(nid)) or {}
|
|
625
649
|
fm = node.get("frontmatter") or {}
|
|
626
650
|
body = node.get("content") or ""
|
|
627
651
|
e_pos, e_neg = _declared(fm, body)
|
|
@@ -769,7 +793,8 @@ def check(cg, content, layer=None, condition_space=None,
|
|
|
769
793
|
if auto_flywheel and verdict in FLYWHEEL_TRIGGERS:
|
|
770
794
|
rec["unresolved_id"] = _fire_flywheel(cg, query or content, verdict,
|
|
771
795
|
reason, missing, allc)
|
|
772
|
-
|
|
796
|
+
if log_write:
|
|
797
|
+
log(cg, rec)
|
|
773
798
|
return rec
|
|
774
799
|
|
|
775
800
|
|
package/md_cg/datapath.py
CHANGED
|
@@ -50,22 +50,49 @@ ENV_AUX_ROOT = "MDCG_AUX_ROOT"
|
|
|
50
50
|
DEFAULT_AUX_DIRNAME = ".mdcg"
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
#: Windows 保留设备名全集(末段直判用;比对前统一 .upper(),故此处全大写):
|
|
54
|
+
#: DOS 设备名 CON/PRN/AUX/NUL、控制台伪设备 CONIN$/CONOUT$、COM1-9/LPT1-9,
|
|
55
|
+
#: 以及 Windows 承认的上标数字变体 COM¹²³/LPT¹²³(上标 0 与 4-9 不是保留名)。
|
|
56
|
+
_RESERVED_DEVICE_NAMES = frozenset(
|
|
57
|
+
["CON", "PRN", "AUX", "NUL", "CONIN$", "CONOUT$"]
|
|
58
|
+
+ [f"COM{i}" for i in range(1, 10)]
|
|
59
|
+
+ [f"LPT{i}" for i in range(1, 10)]
|
|
60
|
+
+ ["COM\u00b9", "COM\u00b2", "COM\u00b3",
|
|
61
|
+
"LPT\u00b9", "LPT\u00b2", "LPT\u00b3"])
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# 生效条件:纯字符串判定(不触盘、无平台分支——平台闸在调用处 _abs_host_path);
|
|
65
|
+
# 传入 path 归一("/" → "\")后去尾随反斜杠取末段,末段按首个 "." 截断取 stem、
|
|
66
|
+
# 再去尾随空格,stem.upper() 命中 _RESERVED_DEVICE_NAMES 即返回 True,否则 False。
|
|
67
|
+
# 末段直判不依赖 OS/Python 版本的 GetFullPathNameW 吞并行为——较新 Windows 构建
|
|
68
|
+
# 不再把「目录\aux」吞成 \\.\aux(issue #57),前缀判据随之失效,本函数是稳定判据。
|
|
69
|
+
def _reserved_device_tail(path: str) -> bool:
|
|
70
|
+
seg = path.replace("/", "\\").rstrip("\\").split("\\")[-1]
|
|
71
|
+
stem = seg.split(".", 1)[0].rstrip(" ")
|
|
72
|
+
return stem.upper() in _RESERVED_DEVICE_NAMES
|
|
73
|
+
|
|
74
|
+
|
|
53
75
|
# 生效条件:p 按仓库既有口径 expanduser+abspath 归一;平台为 Windows(os.name=="nt")
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
76
|
+
# 且命中两因之一时抛 ValueError——① 归一结果以设备命名空间前缀("\\\\.\\")开头:
|
|
77
|
+
# 保留设备名末段(aux/con/nul/prn/com1-9/lpt1-9 等)被 GetFullPathNameW 吞成设备
|
|
78
|
+
# 路径(例 D:\sandbox\aux → \\.\aux);② 归一结果末段直判为保留设备名
|
|
79
|
+
# (_reserved_device_tail):较新 Windows 构建不再吞并(issue #57),旧前缀判据
|
|
80
|
+
# 失效,末段直判不依赖 OS 行为——两因并置互为兜底。后果同:目录语义静默丢失,
|
|
81
|
+
# 密钥/令牌/数据覆盖键随之静默失联;非 Windows 平台该形态是合法目录名字面量,
|
|
82
|
+
# 不判定。归一是纯字符串操作不触盘,路径无需存在即可复现。env_key 传覆盖键名
|
|
83
|
+
# (env 变量名或 paths.json 键),仅用于错误消息定位误配来源;为空时消息以
|
|
84
|
+
# 「该路径」指代。
|
|
60
85
|
def _abs_host_path(p: str, env_key: str = "") -> str:
|
|
61
86
|
r = os.path.abspath(os.path.expanduser(p))
|
|
62
|
-
if os.name == "nt" and r.startswith("\\\\.\\")
|
|
87
|
+
if os.name == "nt" and (r.startswith("\\\\.\\")
|
|
88
|
+
or _reserved_device_tail(r)):
|
|
63
89
|
who = env_key or "该路径"
|
|
64
90
|
raise ValueError(
|
|
65
|
-
f"{who}
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
|
|
91
|
+
f"{who} 归一后末段是 Windows 保留设备名({r!r}):GetFullPathNameW "
|
|
92
|
+
"可能把整个目录吞成设备路径(\\\\.\\aux 形态,目录语义静默丢失);"
|
|
93
|
+
"较新 Windows 构建即便不吞并,保留设备名末段也一律按误配拒绝——"
|
|
94
|
+
"本守卫末段直判不依赖 OS 吞并行为(issue #57)。请把 "
|
|
95
|
+
f"{who} 改指向末段不含保留设备名的普通目录。")
|
|
69
96
|
return r
|
|
70
97
|
|
|
71
98
|
|
package/md_cg/forgetting.py
CHANGED
|
@@ -174,7 +174,7 @@ def self_coverage(cg, node_id, content):
|
|
|
174
174
|
return round(_coverage(bigrams(payload(content)), body), 4)
|
|
175
175
|
|
|
176
176
|
|
|
177
|
-
# 生效条件:content 经 payload/bigrams 得空集合时直接返回零值 best(max=0.0、with=None、compared=0
|
|
177
|
+
# 生效条件:content 经 payload/bigrams 得空集合时直接返回零值 best(max=0.0、with=None、compared=0);否则**循环外经 cg._maybe_reload_index() 单探一次**(N276:无该方法/不可调用的载体跳过,环内回落原 cg.get(nid)),遍历 cg.index 的 nodes,跳过 nid==exclude,layer 为真值时只比较 str(layer 字段 or "")==layer 的节点,cg.get(nid, probe=False)(无探活面的载体回落 cg.get(nid))抛异常/返回假值、或该节点 content 的 bigrams 为空则跳过,每计入一个节点后若 n>=limit 立即 break(故 limit 为 0 或负数时只比较首项即停),返回覆盖度最大者 best(无覆盖度提升时不更新 with/jaccard,compared 为实际计入数)。
|
|
178
178
|
def redundancy(cg, content, layer="contextual", exclude=None, limit=MAX_COMPARE):
|
|
179
179
|
"""Q1 重复?——新内容被既有同层节点覆盖的最大比例。"""
|
|
180
180
|
new = bigrams(payload(content))
|
|
@@ -182,6 +182,18 @@ def redundancy(cg, content, layer="contextual", exclude=None, limit=MAX_COMPARE)
|
|
|
182
182
|
if not new:
|
|
183
183
|
return best
|
|
184
184
|
nodes = ((getattr(cg, "index", None) or {}).get("nodes") or {})
|
|
185
|
+
# N276(第 32 轮性能面):**批内代际探针只做一次**——循环里每次 cg.get
|
|
186
|
+
# 都会经 MdCG.get 首行重算索引签名(快照 stat + listdir + 逐分片 stat,
|
|
187
|
+
# 单点实测 ~38µs;本库 240 次/调用全同签名重探)。此处循环外探一次、
|
|
188
|
+
# 环内 cg.get(nid, probe=False)。语义窗口=**本调用期间「他进程写入」
|
|
189
|
+
# 不被探知**(毫秒级;读取环本就按当次快照判定,与读缓存/热缓存已声明
|
|
190
|
+
# 的进程内一致性边界同款,故注释记账即可);无探活面的载体(测试桩)
|
|
191
|
+
# 回落原 cg.get(nid),行为零变。
|
|
192
|
+
_probe = getattr(cg, "_maybe_reload_index", None)
|
|
193
|
+
if callable(_probe):
|
|
194
|
+
_probe()
|
|
195
|
+
else:
|
|
196
|
+
_probe = None
|
|
185
197
|
n = 0
|
|
186
198
|
for nid in list(nodes.keys()):
|
|
187
199
|
if nid == exclude:
|
|
@@ -189,7 +201,8 @@ def redundancy(cg, content, layer="contextual", exclude=None, limit=MAX_COMPARE)
|
|
|
189
201
|
if layer and str(nodes[nid].get("layer") or "") != layer:
|
|
190
202
|
continue
|
|
191
203
|
try:
|
|
192
|
-
node = cg.get(nid)
|
|
204
|
+
node = (cg.get(nid, probe=False) if _probe is not None
|
|
205
|
+
else cg.get(nid))
|
|
193
206
|
except Exception:
|
|
194
207
|
node = None
|
|
195
208
|
if not node:
|
package/md_cg/freshness.py
CHANGED
|
@@ -179,22 +179,31 @@ def resolve_gamma(gamma=None) -> float:
|
|
|
179
179
|
# 核(一律经 time_core;本模块不写指数核)
|
|
180
180
|
# --------------------------------------------------------------------------
|
|
181
181
|
|
|
182
|
-
# 生效条件:gamma 与 dt_days(单位=天)任意可转 float 时,返回 time_core.cred_factor(gamma, dt_days, floor, ceil);dt 非有限值(inf/nan)时同样交给 cred_factor(inf → 取 floor);
|
|
182
|
+
# 生效条件:gamma 与 dt_days(单位=天)任意可转 float 时,返回 time_core.cred_factor(gamma, dt_days, floor, ceil);dt 非有限值(inf/nan)时同样交给 cred_factor(inf → 取 floor);enabled 为 None(缺省)时自算(模块级 enabled()),显式传 bool 时按传入值——两者皆假值即恒返回 1.0;
|
|
183
183
|
def decay_factor(gamma: float, dt_days: float, floor: float = SCORE_FLOOR,
|
|
184
|
-
ceil: float = SCORE_CEIL) -> float:
|
|
184
|
+
ceil: float = SCORE_CEIL, enabled=None) -> float:
|
|
185
185
|
"""时间衰减乘子 `e^(-γ·Δt)`(**核来自唯一权威** `time_core.cred_factor`)。
|
|
186
186
|
|
|
187
187
|
⚠ `dt_days` 的单位**必须**是「天」(`DT_UNIT`);调用方负责换算,
|
|
188
188
|
换算单点是 `entry_weight`(`(now - created_at) / SECONDS_PER_DAY`)。
|
|
189
|
+
|
|
190
|
+
`enabled`(N274):None=自算(默认,行为与改动前逐位一致);显式传
|
|
191
|
+
bool 时不再自算——供 `entry_weight`/`_score` 把开关**循环外解析一次**
|
|
192
|
+
后透传,消掉逐节点 env 读取。自算分支写 `globals()["enabled"]()` 而非
|
|
193
|
+
直接 `enabled()`:形参同名会遮蔽模块函数,而 `globals()` 的解析语义与
|
|
194
|
+
改前那句 `enabled()` 逐位一致(含测试对模块函数的打桩/替换面)。
|
|
189
195
|
"""
|
|
190
|
-
if
|
|
196
|
+
if enabled is None:
|
|
197
|
+
enabled = globals()["enabled"]()
|
|
198
|
+
if not enabled:
|
|
191
199
|
return 1.0
|
|
192
200
|
return _tc.cred_factor(float(gamma), float(dt_days), float(floor),
|
|
193
201
|
float(ceil))
|
|
194
202
|
|
|
195
203
|
|
|
196
|
-
# 生效条件:access_count 与 last_access 任意(不可转数值按 0 处理)、importance 任意可转 float
|
|
197
|
-
def refresh_factor(access_count=0, last_access=0, importance=0.0
|
|
204
|
+
# 生效条件:access_count 与 last_access 任意(不可转数值按 0 处理)、importance 任意可转 float;enabled 为 None(缺省)时自算(模块级 enabled()),显式传 bool 时按传入值——两者皆假值即恒返回 1.0;否则按 REFRESH_PARAMS 的三档判定返回 1.0+gain / 1.0−gain / 1.0;
|
|
205
|
+
def refresh_factor(access_count=0, last_access=0, importance=0.0,
|
|
206
|
+
enabled=None) -> float:
|
|
198
207
|
"""访问刷新/降权乘子(**门槛与步长照抄 AEIS** `consolidate_cycle`)。
|
|
199
208
|
|
|
200
209
|
判定序(与 AEIS 同序同门):
|
|
@@ -209,8 +218,13 @@ def refresh_factor(access_count=0, last_access=0, importance=0.0) -> float:
|
|
|
209
218
|
在**从未被访问**(`access_count == 0`)时也为真——照抄原样会让「刚写、
|
|
210
219
|
从未调用的高重要度节点」白拿一次刷新。加此闸后「未被调用」只能不刷新,
|
|
211
220
|
不会反被奖励(语义仍与 §7.1「被经常调用的记忆其权重被刷新」一致)。
|
|
221
|
+
|
|
222
|
+
`enabled`(N274):None=自算(默认,行为与改动前逐位一致);显式传
|
|
223
|
+
bool 时不再自算——与 `decay_factor` 同一透传口径(见其 docstring)。
|
|
212
224
|
"""
|
|
213
|
-
if
|
|
225
|
+
if enabled is None:
|
|
226
|
+
enabled = globals()["enabled"]()
|
|
227
|
+
if not enabled:
|
|
214
228
|
return 1.0
|
|
215
229
|
p = REFRESH_PARAMS
|
|
216
230
|
try:
|
|
@@ -251,8 +265,8 @@ def protected_floor(entry) -> float:
|
|
|
251
265
|
return max(SCORE_FLOOR, min(1.0, IMPORTANCE_PROTECT / imp))
|
|
252
266
|
|
|
253
267
|
|
|
254
|
-
# 生效条件:entry 为可取 created_at / access_count / last_access / importance / protected 的映射;fm 为真值时上述五个键优先取自 fm(缺键回落 entry);now 为假值(None)时取 freshness_now()
|
|
255
|
-
def entry_weight(entry, now=None, gamma=None, fm=None) -> tuple:
|
|
268
|
+
# 生效条件:entry 为可取 created_at / access_count / last_access / importance / protected 的映射;fm 为真值时上述五个键优先取自 fm(缺键回落 entry);now 为假值(None)时取 freshness_now();enabled 为 None(缺省)时自算(模块级 enabled()),显式传 bool 时按传入值——两者皆假值即早退返回 (1.0, {"disabled": True})(早退先于 now/γ 解析,与改前同序);否则无条件返回 (乘子, 依据 dict)——乘子 = decay_factor(gamma, |now-created_at|/86400, protected_floor(取数后的映射), SCORE_CEIL, enabled=…) × refresh_factor(..., enabled=…);created_at 缺失/不可转数值/≤0 时 dt_days 取 inf(判「距参照无穷远」,由 floor 兜住);
|
|
269
|
+
def entry_weight(entry, now=None, gamma=None, fm=None, enabled=None) -> tuple:
|
|
256
270
|
"""索引条目 → 分数乘子 `(乘子, 依据)`(**检索期唯一取数点**,免读文件)。
|
|
257
271
|
|
|
258
272
|
`entry` 直接来自索引快照(`_node_entry` 已落 `created_at` /
|
|
@@ -260,6 +274,11 @@ def entry_weight(entry, now=None, gamma=None, fm=None) -> tuple:
|
|
|
260
274
|
|
|
261
275
|
`fm` 为可选覆盖源(写面刚落的 frontmatter 可能比索引条目新):只为
|
|
262
276
|
避免在热路径上拷贝 dict 而设,取数优先级 `fm > entry > 缺省`。
|
|
277
|
+
|
|
278
|
+
`enabled`(N274):None=自算(默认,行为与改动前逐位一致);显式传
|
|
279
|
+
bool 时不再自算,且向下透传给 `decay_factor`/`refresh_factor`——它们
|
|
280
|
+
同样不再各自读 env。自算分支经 `globals()["enabled"]()`(形参遮蔽,
|
|
281
|
+
解析语义同改前的 `enabled()`,见 `decay_factor` 同注)。
|
|
263
282
|
"""
|
|
264
283
|
e = entry or {}
|
|
265
284
|
keys = ("created_at", "access_count", "last_access", "importance",
|
|
@@ -269,7 +288,9 @@ def entry_weight(entry, now=None, gamma=None, fm=None) -> tuple:
|
|
|
269
288
|
for k in keys}
|
|
270
289
|
else:
|
|
271
290
|
src = {k: e.get(k) for k in keys}
|
|
272
|
-
if
|
|
291
|
+
if enabled is None:
|
|
292
|
+
enabled = globals()["enabled"]()
|
|
293
|
+
if not enabled:
|
|
273
294
|
return 1.0, {"disabled": True}
|
|
274
295
|
g = resolve_gamma(gamma)
|
|
275
296
|
ref = freshness_now() if now is None else float(now)
|
|
@@ -286,9 +307,9 @@ def entry_weight(entry, now=None, gamma=None, fm=None) -> tuple:
|
|
|
286
307
|
# 微变)且两侧跨语言同值。详见 DT_QUANTUM_DAYS 的说明。
|
|
287
308
|
dt_days = float(int(dt_days / DT_QUANTUM_DAYS)) * DT_QUANTUM_DAYS
|
|
288
309
|
floor = protected_floor(src)
|
|
289
|
-
d = decay_factor(g, dt_days, floor, SCORE_CEIL)
|
|
310
|
+
d = decay_factor(g, dt_days, floor, SCORE_CEIL, enabled=enabled)
|
|
290
311
|
r = refresh_factor(src.get("access_count"), src.get("last_access"),
|
|
291
|
-
src.get("importance"))
|
|
312
|
+
src.get("importance"), enabled=enabled)
|
|
292
313
|
return d * r, {"gamma": g, "dt_days": dt_days, "unit": DT_UNIT,
|
|
293
314
|
"decay": d, "refresh": r, "floor": floor,
|
|
294
315
|
"protected": bool(src.get("protected"))}
|