@furongjun1999/dsh-memory 0.7.4 → 0.8.0
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 +632 -630
- package/codebuddy/CODEBUDDY.md +5 -5
- package/docs/discipline/templates/zcode-user.md.tmpl +4 -1
- package/docs/eval/issue64_replay_check_/346/240/270/351/252/214/344/270/216/344/277/256/345/244/215_v1.0.md +284 -0
- package/docs/eval//345/217/221/345/270/20312_/344/270/226/347/225/214/346/250/241/345/236/213/345/212/237/350/203/275/347/253/257_v1.0.md +1 -1
- package/docs/eval//345/217/221/345/270/20313_/344/270/212/344/270/213/346/226/207/350/207/252/347/256/241/347/220/206/346/234/272/345/210/266_v1.0.md +114 -0
- package/docs/eval//345/217/221/345/270/20314_/350/272/253/344/275/223/303/227/350/204/221/347/273/204/345/220/210_v1.0.md +152 -0
- package/docs/eval//351/262/270/345/250/230/344/270/203/347/261/273/347/212/266/346/200/201/350/277/275/350/270/252_v1.0.md +176 -0
- package/docs/mdcg//344/270/226/347/225/214/346/250/241/345/236/213/345/212/237/350/203/275/347/253/257_/344/275/277/347/224/250/344/270/216/350/277/220/347/273/264_v1.0.md +92 -13
- 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 +43 -43
- package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +14 -3
- 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 +85 -1
- package/docs/plans//347/201/265/346/236/242/350/272/253/344/275/223/303/227/350/204/221_/344/270/226/347/225/214/346/250/241/345/236/213/345/257/271/346/216/245/350/256/276/350/256/241_v0.1.md +119 -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 +461 -461
- package/docs//350/256/260/345/277/206/347/263/273/347/273/237/345/215/263/347/231/275/347/256/261/345/214/226/346/231/272/350/203/275_/346/236/266/346/236/204/345/257/271/347/205/247_v0.2.md +121 -0
- package/docs//350/256/260/345/277/206/347/263/273/347/273/237/345/215/263/347/231/275/347/256/261/345/214/226/346/231/272/350/203/275_/346/236/266/346/236/204/345/257/271/347/205/247_v0.3.md +117 -0
- package/lib/hooks.d.ts +34 -0
- package/lib/hooks.js +165 -0
- package/lib/index.js +22 -1
- package/lib/lib/mdcg_client.d.ts +37 -0
- package/lib/lib/mdcg_client.js +44 -0
- package/md_cg/audit.py +7 -1
- package/md_cg/bench_governance.py +4 -1
- package/md_cg/bench_locomo_zh.py +12 -2
- package/md_cg/bench_zh_mad.py +12 -2
- package/md_cg/chain.py +23 -7
- package/md_cg/consolidate.py +81 -10
- package/md_cg/interop.py +25 -9
- package/md_cg/lifecycle.py +7 -0
- package/md_cg/mcp_server.py +22 -0
- package/md_cg/mdcg.py +6 -1
- package/md_cg/scrub.py +4 -2
- package/md_cg/sleep.py +273 -103
- package/md_cg/sustain.py +150 -4
- package/md_cg/test_lifecycle_retire_leak.py +90 -0
- package/md_cg/test_p6_consolidate.py +389 -7
- package/md_cg/test_sleep_gitlock.py +134 -0
- package/md_cg/test_spatial_coords3d.py +292 -0
- package/md_cg/test_sustain_bounded.py +624 -0
- package/md_cg/units.py +4 -1
- package/package.json +2 -2
- package/skills/plugin.json +1 -1
- package/src/hooks.ts +177 -0
- package/src/index.ts +22 -1
- package/src/lib/mdcg_client.ts +48 -0
- package/zcode/AGENTS.md +5 -5
package/lib/lib/mdcg_client.js
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
* 外部裁决回填 → MdcgClient.verify() → MCP cg(op=verify)
|
|
20
20
|
* 最近记忆时间线 → MdcgClient.timeline() → MCP stg(op=timeline)
|
|
21
21
|
* 近期事件窗口 → MdcgClient.recent() → MCP cg(op=recent)
|
|
22
|
+
* 窗口追加事件 → MdcgClient.recentAdd() → MCP cg(op=recent, action=add)
|
|
23
|
+
* 会话续接包 → MdcgClient.sessionRecall() → MCP cg(op=session, action=recall)
|
|
22
24
|
* 身份读取 → MdcgClient.identity() → MCP cg(op=identity)
|
|
23
25
|
* 白箱能力验证 → MdcgClient.whitebox() → MCP cg(op=whitebox)
|
|
24
26
|
* 服务信息 → MdcgClient.serviceInfo()→ MCP cg(op=info)
|
|
@@ -215,6 +217,48 @@ export class MdcgClient {
|
|
|
215
217
|
recent(limit = 20) {
|
|
216
218
|
return this.cg({ op: 'recent', limit });
|
|
217
219
|
}
|
|
220
|
+
/** 追加一条**近期事件窗口**记录(`cg(op=recent, action=add)` →
|
|
221
|
+
* `md_cg` 的 `remember_event(role, text, tags, meta, window)`)。
|
|
222
|
+
*
|
|
223
|
+
* 这是「滑动窗口」写侧:事件落 `_recent.jsonl`(**运行态面**),按窗口滚动
|
|
224
|
+
* 淘汰(服务端缺省 200 条,`mdcg.py:91 DEFAULT_RECENT_WINDOW`)——它**不是
|
|
225
|
+
* 知识节点**:不占 knowledge 层、不进检索正排,与 `remember()` 的知识面沉淀
|
|
226
|
+
* 是两条独立的轨道(见 src/hooks.ts 文件头的 contextWindow 头注)。
|
|
227
|
+
*
|
|
228
|
+
* ⚠️ **不注入 as_unit**(与 `write()` 同款理由的反面):本调用不写任何层的
|
|
229
|
+
* 节点(服务端 `remember_event` 不做层白名单校验),收窄单元无收益且可能压低
|
|
230
|
+
* 事件密级(缺省 internal);`meta.session` 由调用方显式给出(沿本文件
|
|
231
|
+
* `remember()` 的 sessionTag 口径)。
|
|
232
|
+
*
|
|
233
|
+
* ⚠️ 服务端对 `meta` 做 `setdefault`(tenant/session/harness/unit,见
|
|
234
|
+
* `mdcos.py:5057 remember_event`):调用方已写的键**不被覆盖**。 */
|
|
235
|
+
recentAdd(role, text, meta = {}, tags = []) {
|
|
236
|
+
return this.cg({ op: 'recent', action: 'add', role, text, meta, tags });
|
|
237
|
+
}
|
|
238
|
+
/** **会话续接包**(`cg(op=session, action=recall)` → `md_cg` 的
|
|
239
|
+
* `session_recall`):一次取回 notes / goals / tasks / **recent 事件窗口** /
|
|
240
|
+
* unresolved / self_state,按 `budget_tokens` 整包裁剪(服务端
|
|
241
|
+
* `mdcos.py:3351-3362`:交替丢 recent / notes 尾部)。
|
|
242
|
+
*
|
|
243
|
+
* 本插件消费其中的 `recent` 段——滑动窗口的**注入面**(见 src/hooks.ts)。
|
|
244
|
+
* `recent_limit` 语义 = 近期事件**条数**(非「轮数」);服务端读取时会按
|
|
245
|
+
* `max(1, recent_limit or 10)` 归一(`mdcos.py:3309`)。
|
|
246
|
+
*
|
|
247
|
+
* ⚠️ 两条服务端事实(决定注入侧的可达性,勿据本方法名臆测):
|
|
248
|
+
* ① `recent` 段取 `recent_events(limit=recent_limit)` —— **不按 session
|
|
249
|
+
* 过滤**(近期事件是运行态滚动窗口,会话归属只写在每条事件的 meta 里);
|
|
250
|
+
* ② budget 是**整包**预算:库内 notes/tasks/self_state 占位越多,同样
|
|
251
|
+
* budget 下 recent 段被裁得越短(实测:空库 + 10 条窗口条目,
|
|
252
|
+
* budget_tokens=600 → recent 8 条;1200 → 10 条)。
|
|
253
|
+
*
|
|
254
|
+
* 只读调用,不注入 as_unit(同其它读路径:读无副作用,收窄只会压低 owner
|
|
255
|
+
* 的 private 读能力)。 */
|
|
256
|
+
sessionRecall(session, recentLimit, budgetTokens) {
|
|
257
|
+
return this.cg({
|
|
258
|
+
op: 'session', action: 'recall',
|
|
259
|
+
session, recent_limit: recentLimit, budget_tokens: budgetTokens,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
218
262
|
/** 最近记忆**时间线**(AEIS `timeline` 的对应物):`stg(op=timeline)` →
|
|
219
263
|
* `{count, limit, items:[{id, layer, start, end, preview}]}`,按时间倒序。
|
|
220
264
|
*
|
package/md_cg/audit.py
CHANGED
|
@@ -456,11 +456,17 @@ def _verify_code(payload, ctx):
|
|
|
456
456
|
# 被测命令只要输出非 gbk 字节,读取线程就抛 UnicodeDecodeError →
|
|
457
457
|
# p.stdout/p.stderr 可能为空 → 下一行的失败证据丢失,
|
|
458
458
|
# 「实测失败」会退化成一句没有依据的 REJECT(对齐 whitebox.py 的写法)。
|
|
459
|
+
# stdin=subprocess.DEVNULL(issue #63 同批加固):不指定 stdin 时子进程
|
|
460
|
+
# 继承父进程 stdin(常驻宿主下是 JSON-RPC 活管道),读 stdin 的测试
|
|
461
|
+
# 命令会悬挂到 timeout 才被 kill——形态对齐 `md_cg/run_tests.py:105`。
|
|
459
462
|
p = subprocess.run(argv, cwd=ctx.get("cwd"), capture_output=True,
|
|
460
463
|
text=True, encoding="utf-8", errors="replace",
|
|
461
|
-
shell=False,
|
|
464
|
+
shell=False, stdin=subprocess.DEVNULL,
|
|
462
465
|
timeout=int(os.environ.get("MDCG_CODE_TEST_TIMEOUT", "60")))
|
|
463
466
|
except (OSError, subprocess.SubprocessError) as exc:
|
|
467
|
+
# TimeoutExpired(SubprocessError 子类)也走这里:DEFER + 文案带异常
|
|
468
|
+
# 名与原文(如 "TimeoutExpired: Command '…' timed out after 60 seconds")
|
|
469
|
+
# ——超时**不吞**(issue #63)。
|
|
464
470
|
return _verdict(DEFER, "code", f"测试无法执行:{type(exc).__name__}: {exc}")
|
|
465
471
|
if p.returncode == 0:
|
|
466
472
|
return _verdict(ACCEPT, "code", f"实测通过:{cmd}")
|
|
@@ -315,9 +315,12 @@ def run(root=None, keep=False):
|
|
|
315
315
|
|
|
316
316
|
def _git_head():
|
|
317
317
|
try:
|
|
318
|
+
# timeout=10 既有;stdin=subprocess.DEVNULL 为 issue #63 同批加固——
|
|
319
|
+
# 不指定 stdin 时子进程继承父进程 stdin(常驻宿主下是 JSON-RPC 活管道)。
|
|
318
320
|
out = subprocess.run(["git", "rev-parse", "--short", "HEAD"],
|
|
319
321
|
capture_output=True, text=True, encoding="utf-8",
|
|
320
|
-
errors="replace", shell=False, timeout=10
|
|
322
|
+
errors="replace", shell=False, timeout=10,
|
|
323
|
+
stdin=subprocess.DEVNULL)
|
|
321
324
|
return (out.stdout or "").strip() or "unknown"
|
|
322
325
|
except Exception: # noqa: BLE001
|
|
323
326
|
return "unknown"
|
package/md_cg/bench_locomo_zh.py
CHANGED
|
@@ -303,6 +303,9 @@ ROW_RE = re.compile(
|
|
|
303
303
|
r"([\d.]+)%\s+([\d.]+)%\s+([\d.]+)\s*$", re.M)
|
|
304
304
|
GATE_RE = re.compile(r"拒答率:([\d.]+)%")
|
|
305
305
|
LINE_RE = re.compile(r"拒答线(正例 hit@1 题 Top-1 分 p10):([\d.]+)")
|
|
306
|
+
#: 单臂评测子进程的超时(秒)——issue #63 同批加固:评测跑批比 git 长,按
|
|
307
|
+
#: 「一臂为分钟级」给 1800s(30min)为安全上界;超时即 kill(不再是无限等待)。
|
|
308
|
+
_EVAL_TIMEOUT_S = 1800
|
|
306
309
|
|
|
307
310
|
|
|
308
311
|
# 生效条件:RUST_BIN 经 os.path.exists 为假时抛 SystemExit;否则以 argv 列表 [RUST_BIN, "--dataset", "lc", "--tag", name, "--lib", lib, "--qfile", qfile](extra 为真值时追加其元素)执行 subprocess.run,返回码非 0 或 stdout 未匹配 ROW_RE 时抛 SystemExit,成功时返回 (got, neg, line),其中 GATE_RE/LINE_RE 未命中时对应值为 None。
|
|
@@ -320,8 +323,15 @@ def run_one(name, lib, qfile, extra=None):
|
|
|
320
323
|
if extra:
|
|
321
324
|
argv += list(extra)
|
|
322
325
|
env = dict(os.environ, PYTHONUTF8="1")
|
|
323
|
-
|
|
324
|
-
|
|
326
|
+
try:
|
|
327
|
+
# timeout + stdin=DEVNULL(issue #63 同批加固):评测跑批有界化,
|
|
328
|
+
# 且不继承父进程 stdin(常驻宿主下是 JSON-RPC 活管道)。
|
|
329
|
+
p = subprocess.run(argv, capture_output=True, text=True,
|
|
330
|
+
encoding="utf-8", errors="replace", env=env, cwd=HERE,
|
|
331
|
+
timeout=_EVAL_TIMEOUT_S,
|
|
332
|
+
stdin=subprocess.DEVNULL)
|
|
333
|
+
except subprocess.TimeoutExpired:
|
|
334
|
+
raise SystemExit(f"[失败] {name}:评测超时(>{_EVAL_TIMEOUT_S}s)已被 kill")
|
|
325
335
|
if p.returncode != 0:
|
|
326
336
|
raise SystemExit(f"[失败] {name}:{(p.stderr or '')[-900:]}")
|
|
327
337
|
got = {}
|
package/md_cg/bench_zh_mad.py
CHANGED
|
@@ -481,6 +481,9 @@ ROW_RE = re.compile(
|
|
|
481
481
|
r"^\s*(precise|temporal|interference|reference)\s+(\d+)\s+"
|
|
482
482
|
r"([\d.]+)%\s+([\d.]+)%\s+([\d.]+)\s*$", re.M)
|
|
483
483
|
GROUPS = ["precise", "temporal", "interference", "reference"]
|
|
484
|
+
#: 单臂评测子进程的超时(秒)——issue #63 同批加固:评测跑批比 git 长,按
|
|
485
|
+
#: 「一臂为分钟级」给 1800s(30min)为安全上界;超时即 kill(不再是无限等待)。
|
|
486
|
+
_EVAL_TIMEOUT_S = 1800
|
|
484
487
|
|
|
485
488
|
|
|
486
489
|
# 生效条件:os.path.exists(RUST_BIN) 为真时以 argv=[RUST_BIN,"--dataset","mad","--tag",name,"--lib",lib](extra 为真值时追加 list(extra))执行 subprocess,返回码非 0 或 ROW_RE 在 stdout 未匹配到任何组时 raise SystemExit,否则返回 {组:(n,hit@1,hit@5,MRR)}。
|
|
@@ -498,8 +501,15 @@ def run_one(name, lib, extra=None):
|
|
|
498
501
|
if extra:
|
|
499
502
|
argv += list(extra)
|
|
500
503
|
env = dict(os.environ, PYTHONUTF8="1")
|
|
501
|
-
|
|
502
|
-
|
|
504
|
+
try:
|
|
505
|
+
# timeout + stdin=DEVNULL(issue #63 同批加固):评测跑批有界化,
|
|
506
|
+
# 且不继承父进程 stdin(常驻宿主下是 JSON-RPC 活管道)。
|
|
507
|
+
p = subprocess.run(argv, capture_output=True, text=True,
|
|
508
|
+
encoding="utf-8", errors="replace", env=env, cwd=HERE,
|
|
509
|
+
timeout=_EVAL_TIMEOUT_S,
|
|
510
|
+
stdin=subprocess.DEVNULL)
|
|
511
|
+
except subprocess.TimeoutExpired:
|
|
512
|
+
raise SystemExit(f"[失败] {name}:评测超时(>{_EVAL_TIMEOUT_S}s)已被 kill")
|
|
503
513
|
if p.returncode != 0:
|
|
504
514
|
raise SystemExit(f"[失败] {name}:{(p.stderr or '')[-900:]}")
|
|
505
515
|
got = {}
|
package/md_cg/chain.py
CHANGED
|
@@ -158,8 +158,8 @@ def node_conditions(cg, nid):
|
|
|
158
158
|
return pos
|
|
159
159
|
|
|
160
160
|
|
|
161
|
-
# 生效条件:cg 已有 _chain_adj 且其 [0] 等于 bool(include_hierarchy)、[1]
|
|
162
|
-
def adjacency(cg, include_hierarchy=True):
|
|
161
|
+
# 生效条件:cg 已有 _chain_adj 且其 [0] 等于 bool(include_hierarchy)、[1] 等于可见性闸存在位、[2] 等于 bool(skip_archived) 时直接返回缓存的 [3];否则以 cg.index["nodes"](无 index 或无该键时视为无节点)逐节点收集 frontmatter.edges 中 edge_target 非空的出边(skip_archived=False 时该轮退役过滤整体旁路——维护面全量口径),include_hierarchy 为真时再为 subgraph.nodes 各合成一条 relation_type="part_of"、confidence=1.0 的层级边,仅对有出边的 nid 建表,写回 cg._chain_adj=(bool(include_hierarchy), 闸存在位, bool(skip_archived), adj) 后返回 adj;skip_archived=True(缺省)时 lifecycle.is_archived 为真的节点出边整体不入表、指向退役目标的边(含层级合成边)截断(判据单点 lifecycle.is_archived,缺键=active fail-open;退役不删除、可显式恢复);cg 提供 _chain_visible(nid) 谓词(读隔离,MdCGSecure 注入)时不可见 nid 的出边整体不入表、指向不可见目标的边(含层级合成边)截断——不可见节点 id、其边条件与下游拓扑对调用方不存在(walk/explain/causal_path/expand_from_seeds 全部消费者同闸);
|
|
162
|
+
def adjacency(cg, include_hierarchy=True, skip_archived=True):
|
|
163
163
|
"""出邻接表:nid → [(target_id, edge_dict)]。
|
|
164
164
|
|
|
165
165
|
来源两处:
|
|
@@ -172,8 +172,8 @@ def adjacency(cg, include_hierarchy=True):
|
|
|
172
172
|
(`cg.index["nodes"]`),不是「只碰种子邻域」。故契约 S3「不走全表」这一句
|
|
173
173
|
对**本函数不成立**,正确表述是「不读正文;邻接构建为 O(N) 索引条目级」。
|
|
174
174
|
代价读数(`scripts/p2p4_probe.py` R7,400 条目确定性小库):冷建 ~0.24ms、
|
|
175
|
-
缓存命中 ~1µs;缓存键 `(include_hierarchy,
|
|
176
|
-
|
|
175
|
+
缓存命中 ~1µs;缓存键 `(include_hierarchy, 闸存在位, skip_archived)` 存于
|
|
176
|
+
`cg._chain_adj`,写入/删除后由 `invalidate_cache` 作废。
|
|
177
177
|
(可选硬化路径 (b1):在索引快照的**边集合**上增量构建邻接——本轮未做,
|
|
178
178
|
如需做见 P3 遗留 B 的二选一。)
|
|
179
179
|
|
|
@@ -184,17 +184,29 @@ def adjacency(cg, include_hierarchy=True):
|
|
|
184
184
|
目标的边截断;谓词缺席(基类)行为零变化。缓存键含闸存在位;闸语义
|
|
185
185
|
实例内稳定(_readable 绑定档判定恒用 principal.session,见 mdcos
|
|
186
186
|
_readable),同一实例不会跨身份串台。
|
|
187
|
+
|
|
188
|
+
退役纪律(《秤》v2.1 §5.2;2026-10-06 接线):`skip_archived=True`(缺省)时
|
|
189
|
+
archived 节点出边整体不入表、指向 archived 目标的边截断——`cg(op=causal,
|
|
190
|
+
action=chain)` 等**读/预测面**(chain()/predict.*)由此不再扩散进退役节点;
|
|
191
|
+
**维护面显式旁路**:`scrub`(去污染抽查,按度数排序的维护写面)传
|
|
192
|
+
`skip_archived=False` 取全量表——退役节点仍可被抽查(判据面 = 默认读/注入
|
|
193
|
+
面必消费、维护/管理面直读,见运维文档 §九.5 面的判据)。判据单点
|
|
194
|
+
`lifecycle.is_archived`(缺键=active fail-open)。
|
|
187
195
|
"""
|
|
188
196
|
cached = getattr(cg, "_chain_adj", None)
|
|
189
197
|
vis = getattr(cg, "_chain_visible", None)
|
|
190
198
|
vis_on = vis is not None
|
|
191
199
|
if (cached is not None and cached[0] == bool(include_hierarchy)
|
|
192
|
-
and cached[1] == vis_on
|
|
193
|
-
|
|
200
|
+
and cached[1] == vis_on
|
|
201
|
+
and cached[2] == bool(skip_archived)):
|
|
202
|
+
return cached[3]
|
|
194
203
|
from . import subgraph as _sg
|
|
204
|
+
from . import lifecycle as _lc
|
|
195
205
|
nodes = ((getattr(cg, "index", None) or {}).get("nodes") or {})
|
|
196
206
|
adj = {}
|
|
197
207
|
for nid in list(nodes):
|
|
208
|
+
if skip_archived and _lc.is_archived(nodes.get(nid)):
|
|
209
|
+
continue # 退役节点:其出边与条件整体不入表(不解析 fm)
|
|
198
210
|
if vis is not None and not vis(nid):
|
|
199
211
|
continue # 不可见节点:其出边与条件整体不可见(不解析 fm)
|
|
200
212
|
fm = _sg._fm(cg, nid)
|
|
@@ -202,11 +214,15 @@ def adjacency(cg, include_hierarchy=True):
|
|
|
202
214
|
for e in (fm.get("edges") or []):
|
|
203
215
|
tgt = edge_target(e)
|
|
204
216
|
if tgt:
|
|
217
|
+
if skip_archived and _lc.is_archived(nodes.get(tgt)):
|
|
218
|
+
continue # 拓扑边界:指向退役目标的边径直截断
|
|
205
219
|
if vis is not None and not vis(tgt):
|
|
206
220
|
continue # 拓扑边界:指向不可见目标的边对调用方不存在
|
|
207
221
|
out.append((tgt, e if isinstance(e, dict) else {"target": tgt}))
|
|
208
222
|
if include_hierarchy:
|
|
209
223
|
for ch in _sg.declared(fm)["nodes"]:
|
|
224
|
+
if skip_archived and _lc.is_archived(nodes.get(ch)):
|
|
225
|
+
continue
|
|
210
226
|
if vis is not None and not vis(ch):
|
|
211
227
|
continue
|
|
212
228
|
out.append((ch, {"target": ch, "relation_type": "part_of",
|
|
@@ -214,7 +230,7 @@ def adjacency(cg, include_hierarchy=True):
|
|
|
214
230
|
if out:
|
|
215
231
|
adj[nid] = out
|
|
216
232
|
try:
|
|
217
|
-
cg._chain_adj = (bool(include_hierarchy), vis_on, adj)
|
|
233
|
+
cg._chain_adj = (bool(include_hierarchy), vis_on, bool(skip_archived), adj)
|
|
218
234
|
except Exception:
|
|
219
235
|
pass
|
|
220
236
|
return adj
|
package/md_cg/consolidate.py
CHANGED
|
@@ -25,11 +25,17 @@
|
|
|
25
25
|
闸门 1 · grounding 支撑度(确定性):候选短语必须能在节点正文里找到字符级依据,
|
|
26
26
|
否则判为幻觉 → REJECT。(对应「不猜测」)
|
|
27
27
|
闸门 2 · replay 回放(确定性):把候选条件当作查询,回放生产检索路径的判定:
|
|
28
|
-
· pos_recall 以「生效条件」为查询 →
|
|
28
|
+
· pos_recall 以「生效条件」为查询 → 本节点应被召回(issue #64:原式另带
|
|
29
|
+
「负条件一票否决」因子,过严误杀共享主题词的合格负条件,
|
|
30
|
+
已撤销——自否定职责由 no_conflict 承担);
|
|
29
31
|
· neg_separated 以「不适用条件」为查询 → 应触发条件级负路由,且负条件与正文
|
|
30
32
|
低相关(负条件必须是「域外」的,不能把知识本身否定掉);
|
|
31
33
|
· no_conflict 生效条件与不适用条件不得互相覆盖。
|
|
32
34
|
三者同时成立才算「条件稳定」。(对应「回放 / 断言 / 回归」)
|
|
35
|
+
闸门 2 前另设**负条件两态拦截**(issue #64,见 consolidate 主流程):
|
|
36
|
+
候选未产出负条件(neg_absent)/ 负条件被 grounding 删光(neg_dropped_all)
|
|
37
|
+
→ 直接 REJECT,不进 replay、不进验证单元、不落盘——「负条件被删光」不得
|
|
38
|
+
被 replay 的空集短路翻译成「通过」(否则缺「不适用条件」要素的节点仍会落盘)。
|
|
33
39
|
闸门 3 · 验证单元(GLM,独立模型):逐条核验候选是否有正文依据、负条件是否真域外。
|
|
34
40
|
硬约束:**验证单元只能否决,不能新增/改写**——它没有产出权,
|
|
35
41
|
否则验证环节自己就成了新的幻觉源。
|
|
@@ -284,6 +290,8 @@ def http_llm(prompt: str, model: str = None, base: str = None, key: str = None,
|
|
|
284
290
|
max_tokens=None 走三级解析(显式 > MDCG_LLM_MAX_TOKENS > DEFAULT_MAX_TOKENS);
|
|
285
291
|
历史 bug(issue #24):曾硬编码 1200——思考模型 reasoning 吃光预算,
|
|
286
292
|
content 空串静默落成 parse_failed,离线固化 100% DEFER。
|
|
293
|
+
timeout 由调用方注入(CLI --timeout / --reflect-timeout / --verify-timeout,
|
|
294
|
+
默认 120 秒)——issue #64:此前 CLI 两 lambda 均未传,恒为硬默认、不可配。
|
|
287
295
|
"""
|
|
288
296
|
if role:
|
|
289
297
|
model, base, key = role_config(role, model, base, key)
|
|
@@ -496,20 +504,25 @@ def grounding_filter(cand: dict, body: str, thresholds: dict = None):
|
|
|
496
504
|
return kept, detail
|
|
497
505
|
|
|
498
506
|
|
|
499
|
-
# 生效条件:给定 pos_terms、neg_terms、body,返回含 pos_recall、neg_separated、no_conflict、ok
|
|
507
|
+
# 生效条件:给定 pos_terms、neg_terms、body,返回含 pos_recall、neg_separated、no_conflict、ok 的回放判定字典;pos_recall 只认「生效条件在正文上有覆盖率」,不含负条件一票否决。
|
|
500
508
|
def replay_check(pos_terms, neg_terms, body: str) -> dict:
|
|
501
509
|
"""回放生产判定:正例召回 + 负例剔除 + 无自相矛盾。
|
|
502
510
|
|
|
503
511
|
复用 _path_semantic 的同一批原语,保证与生产路同源(P6 与真实路做一致性回归)。
|
|
512
|
+
|
|
513
|
+
issue #64(2026-10-06):pos_recall 原式带 `and not _neg_hit(tw_pos, neg_terms)`
|
|
514
|
+
一票否决——「生效条件任一词整词出现在不适用条件里」即判负;而负条件描述的正是
|
|
515
|
+
**邻近易混情境**,与生效条件共享领域主题词是结构必然(不共享主题词就谈不上「邻近」),
|
|
516
|
+
该因子把大量合格负条件误杀为「正例召回失败」。自否定职责已由第 3 条 no_conflict
|
|
517
|
+
以覆盖率口径(<0.5 才算互相覆盖)承担——本因子与它同意图且更严,故撤销。
|
|
504
518
|
"""
|
|
505
519
|
pos_text = " ".join(pos_terms or [])
|
|
506
520
|
neg_text = " ".join(neg_terms or [])
|
|
507
521
|
tw_pos = expand_query_terms_weighted(pos_text) if pos_text else {}
|
|
508
522
|
tw_neg = expand_query_terms_weighted(neg_text) if neg_text else {}
|
|
509
523
|
|
|
510
|
-
# 1.
|
|
511
|
-
pos_recall = bool(pos_text) and _weighted_coverage(tw_pos, body) > 0.0
|
|
512
|
-
and not _neg_hit(tw_pos, neg_terms)
|
|
524
|
+
# 1. 正例:以生效条件为查询,本节点正文应被命中(不含负条件一票否决,见 docstring)
|
|
525
|
+
pos_recall = bool(pos_text) and _weighted_coverage(tw_pos, body) > 0.0
|
|
513
526
|
|
|
514
527
|
# 2. 负例:以不适用条件为查询,应触发条件级负路由;且负条件与正文低相关
|
|
515
528
|
# (负条件必须是「域外」的,若与正文强相关,等于让知识否定自己)
|
|
@@ -652,7 +665,7 @@ def _apply_node(cg, e, fm: dict, content: str, kept: dict, prov: dict,
|
|
|
652
665
|
before=before, after=evolution.state_of(cg, nid) or {})
|
|
653
666
|
|
|
654
667
|
|
|
655
|
-
# 生效条件:给定 root
|
|
668
|
+
# 生效条件:给定 root,扫描正排层节点并执行反思→白箱闸门(含负条件两态拦截)→验证→固化,返回报表 rep;require_verify=True 且无 verify_fn 时全部 DEFER。
|
|
656
669
|
def consolidate(root: str, layer: str = None, limit: int = None, apply: bool = False,
|
|
657
670
|
overwrite: bool = False, llm_fn=None, reflect_fn=None,
|
|
658
671
|
verify_fn=None, reflect_model: str = "", verify_model: str = "",
|
|
@@ -663,6 +676,10 @@ def consolidate(root: str, layer: str = None, limit: int = None, apply: bool = F
|
|
|
663
676
|
|
|
664
677
|
llm_fn 是 reflect_fn 的旧名(向后兼容,单模型模式)。
|
|
665
678
|
require_verify=True 且无 verify_fn → 一律 DEFER(纪律 5:未经验证不固化)。
|
|
679
|
+
白箱闸门内先过**负条件两态拦截**(issue #64):候选未产出负条件 → reasons
|
|
680
|
+
记 neg_absent;负条件被 grounding 删光 → reasons 记 neg_dropped_all;两态
|
|
681
|
+
都直接 REJECT(不进 replay / 不进验证单元 / 不落盘)——缺「不适用条件」
|
|
682
|
+
要素的节点不得因空集短路被判「通过」。
|
|
666
683
|
"""
|
|
667
684
|
reflect_fn = reflect_fn or llm_fn
|
|
668
685
|
cg = MdCGOS(root)
|
|
@@ -728,6 +745,36 @@ def consolidate(root: str, layer: str = None, limit: int = None, apply: bool = F
|
|
|
728
745
|
kept, gdetail = grounding_filter(cand, body, thresholds)
|
|
729
746
|
pos = kept.get("生效条件") or []
|
|
730
747
|
neg = kept.get("不适用条件") or []
|
|
748
|
+
|
|
749
|
+
# 2a) 负条件两态拦截(issue #64,2026-10-06):「不适用条件」是 CCG 必需要素,
|
|
750
|
+
# 候选**未产出**(neg_absent)或**产出了但被 grounding 删光**(neg_dropped_all)
|
|
751
|
+
# 时都必须 REJECT——不进 replay、不进验证单元、不落盘。
|
|
752
|
+
# 修复前把空 neg 交给 replay_check,命中其 `else: neg_separated = True` 短路,
|
|
753
|
+
# 「负条件被删光/未产出」被翻译成「通过」,缺要素节点照常落盘并携带
|
|
754
|
+
# verification_basis 声明(B 类实测:accepted=1/written=1,正文无
|
|
755
|
+
# 「# 不适用条件」行)。两态各自计数,便于报表区分「模型没产出」与
|
|
756
|
+
# 「产出被 grounding 判为幻觉丢弃」。
|
|
757
|
+
neg_cand = cand.get("不适用条件") or []
|
|
758
|
+
if not isinstance(neg_cand, list):
|
|
759
|
+
neg_cand = [neg_cand]
|
|
760
|
+
neg_cand = [t for t in neg_cand if str(t).strip()]
|
|
761
|
+
if not neg_cand:
|
|
762
|
+
rep["rejected"] += 1
|
|
763
|
+
_bump("neg_absent")
|
|
764
|
+
if verbose and len(rep["samples"]) < 8:
|
|
765
|
+
rep["samples"].append({"id": nid, "verdict": "REJECT",
|
|
766
|
+
"stage": "whitebox", "reason": "neg_absent",
|
|
767
|
+
"grounding": gdetail, "replay": None})
|
|
768
|
+
continue
|
|
769
|
+
if not neg:
|
|
770
|
+
rep["rejected"] += 1
|
|
771
|
+
_bump("neg_dropped_all")
|
|
772
|
+
if verbose and len(rep["samples"]) < 8:
|
|
773
|
+
rep["samples"].append({"id": nid, "verdict": "REJECT",
|
|
774
|
+
"stage": "whitebox", "reason": "neg_dropped_all",
|
|
775
|
+
"grounding": gdetail, "replay": None})
|
|
776
|
+
continue
|
|
777
|
+
|
|
731
778
|
replay = replay_check(pos, neg, body)
|
|
732
779
|
if not kept or not replay["ok"]:
|
|
733
780
|
rep["rejected"] += 1
|
|
@@ -1477,9 +1524,21 @@ def _cli(argv=None) -> int:
|
|
|
1477
1524
|
ap.add_argument("--min-grounding", type=float, default=None,
|
|
1478
1525
|
help="统一 grounding 阈值(默认按字段 0.5 / 不适用条件 0.34)")
|
|
1479
1526
|
ap.add_argument("--max-tokens", type=int, default=None,
|
|
1480
|
-
help=f"LLM
|
|
1527
|
+
help=f"LLM 输出预算共用缺省(含思考模型 reasoning_tokens;默认 "
|
|
1481
1528
|
f"{DEFAULT_MAX_TOKENS},可 env {MAX_TOKENS_ENV} 覆盖;"
|
|
1482
|
-
"子代理配置标准 v0.5 §1
|
|
1529
|
+
"子代理配置标准 v0.5 §1);两角色可分别用 "
|
|
1530
|
+
"--reflect-max-tokens / --verify-max-tokens 单设")
|
|
1531
|
+
ap.add_argument("--reflect-max-tokens", type=int, default=None,
|
|
1532
|
+
help="反思单元输出预算(缺省回落 --max-tokens)")
|
|
1533
|
+
ap.add_argument("--verify-max-tokens", type=int, default=None,
|
|
1534
|
+
help="验证单元输出预算(缺省回落 --max-tokens)")
|
|
1535
|
+
ap.add_argument("--timeout", type=float, default=120,
|
|
1536
|
+
help="LLM HTTP 超时秒数(缺省 120);两角色可分别用 "
|
|
1537
|
+
"--reflect-timeout / --verify-timeout 单设")
|
|
1538
|
+
ap.add_argument("--reflect-timeout", type=float, default=None,
|
|
1539
|
+
help="反思单元 HTTP 超时(缺省回落 --timeout)")
|
|
1540
|
+
ap.add_argument("--verify-timeout", type=float, default=None,
|
|
1541
|
+
help="验证单元 HTTP 超时(缺省回落 --timeout)")
|
|
1483
1542
|
ap.add_argument("--no-llm", action="store_true",
|
|
1484
1543
|
help="不调用 LLM,只做四要素完整性普查")
|
|
1485
1544
|
ap.add_argument("--check", action="store_true",
|
|
@@ -1530,16 +1589,28 @@ def _cli(argv=None) -> int:
|
|
|
1530
1589
|
f"({_ROLE_ENV[REFLECT_ROLE][2]} / DEEPSEEK_API_KEY)→ 退化为普查模式",
|
|
1531
1590
|
file=sys.stderr)
|
|
1532
1591
|
else:
|
|
1592
|
+
# 单值 --max-tokens / --timeout 继续作两角色共用缺省;角色级参数缺省
|
|
1593
|
+
# None → 回落共用缺省。issue #64:此前单值被同时喂 reflect/verify,
|
|
1594
|
+
# timeout 恒为 http_llm 默认 120 不可配(生产两 lambda 均未传)——
|
|
1595
|
+
# 两侧预算/超时需求可不同,须能分别单设。
|
|
1596
|
+
refl_mt = (a.reflect_max_tokens if a.reflect_max_tokens is not None
|
|
1597
|
+
else a.max_tokens)
|
|
1598
|
+
refl_to = (a.reflect_timeout if a.reflect_timeout is not None
|
|
1599
|
+
else a.timeout)
|
|
1533
1600
|
reflect_fn = (lambda p: http_llm(p, role=REFLECT_ROLE, # noqa: E731
|
|
1534
1601
|
model=a.reflect_model,
|
|
1535
|
-
max_tokens=
|
|
1602
|
+
max_tokens=refl_mt, timeout=refl_to))
|
|
1536
1603
|
if a.self_verify:
|
|
1537
1604
|
verify_fn = reflect_fn
|
|
1538
1605
|
elif not a.no_verify:
|
|
1539
1606
|
if v_key:
|
|
1607
|
+
ver_mt = (a.verify_max_tokens
|
|
1608
|
+
if a.verify_max_tokens is not None else a.max_tokens)
|
|
1609
|
+
ver_to = (a.verify_timeout if a.verify_timeout is not None
|
|
1610
|
+
else a.timeout)
|
|
1540
1611
|
verify_fn = (lambda p: http_llm(p, role=VERIFY_ROLE, # noqa: E731
|
|
1541
1612
|
model=a.verify_model,
|
|
1542
|
-
max_tokens=
|
|
1613
|
+
max_tokens=ver_mt, timeout=ver_to))
|
|
1543
1614
|
else:
|
|
1544
1615
|
print(f"[consolidate] 验证单元未配置 key"
|
|
1545
1616
|
f"({_ROLE_ENV[VERIFY_ROLE][2]} / ZHIPU_API_KEY / GLM_API_KEY)"
|
package/md_cg/interop.py
CHANGED
|
@@ -47,6 +47,11 @@ _FORBIDDEN_RES = [
|
|
|
47
47
|
]
|
|
48
48
|
_FORBIDDEN_KEYS = {"prompt", "content", "spec", "api_key", "base", "model",
|
|
49
49
|
"system_prompt", "user_prompt", "content_head"}
|
|
50
|
+
#: git add/commit 子进程的超时(秒)——issue #63 同批加固:本机 git 为秒级
|
|
51
|
+
#: 操作,120s 为安全上界;超时即 kill。**stdin=subprocess.DEVNULL 同加**:
|
|
52
|
+
#: 不指定 stdin 时子进程继承父进程 stdin(常驻宿主下是 JSON-RPC 活管道),
|
|
53
|
+
#: 读 stdin 的子进程会悬挂(先例形态 `md_cg/run_tests.py:105`)。
|
|
54
|
+
_GIT_TIMEOUT_S = 120
|
|
50
55
|
|
|
51
56
|
|
|
52
57
|
class InteropSanityError(ValueError):
|
|
@@ -323,15 +328,26 @@ def write_verdict_to_repo(verdict: dict, repo: str = HERE,
|
|
|
323
328
|
out = {"ok": True, "path": fp}
|
|
324
329
|
if do_commit:
|
|
325
330
|
import subprocess
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
331
|
+
try:
|
|
332
|
+
r1 = subprocess.run(["git", "add", os.path.relpath(fp, repo)],
|
|
333
|
+
cwd=repo, capture_output=True, text=True,
|
|
334
|
+
encoding="utf-8", errors="replace",
|
|
335
|
+
timeout=_GIT_TIMEOUT_S,
|
|
336
|
+
stdin=subprocess.DEVNULL)
|
|
337
|
+
r2 = subprocess.run(
|
|
338
|
+
["git", "commit", "-q", "-m",
|
|
339
|
+
f"interop(verifier): iter={it} verdict={verdict.get('verdict')} "
|
|
340
|
+
f"passed={verdict.get('passed')} failed={verdict.get('failed')}"],
|
|
341
|
+
cwd=repo, capture_output=True, text=True,
|
|
342
|
+
encoding="utf-8", errors="replace",
|
|
343
|
+
timeout=_GIT_TIMEOUT_S, stdin=subprocess.DEVNULL)
|
|
344
|
+
except subprocess.TimeoutExpired:
|
|
345
|
+
# 超时**不吞**:如实记进既有错误面(与 rc≠0 同一条 commit_err),
|
|
346
|
+
# 不假装提交成功(issue #63)。
|
|
347
|
+
out["committed"] = False
|
|
348
|
+
out["commit_err"] = ("git 超时(%ss)已被 kill:%s" %
|
|
349
|
+
(_GIT_TIMEOUT_S, out_dir))
|
|
350
|
+
return out
|
|
335
351
|
out["committed"] = r2.returncode == 0
|
|
336
352
|
if r2.returncode != 0:
|
|
337
353
|
out["commit_err"] = (r2.stderr or r1.stderr)[:200]
|
package/md_cg/lifecycle.py
CHANGED
|
@@ -280,6 +280,13 @@ def set_state(cg, node_id: str, dst: str, reason: str = None,
|
|
|
280
280
|
cg._write_node(node_id, os.path.join(cg.root, node["path"]), fm,
|
|
281
281
|
node.get("content") or "")
|
|
282
282
|
_sync_index(cg, node_id, fm)
|
|
283
|
+
# 退役接线(2026-10-06):状态迁移改变 `is_archived` 读数 ⇒ 依赖该判据的
|
|
284
|
+
# 邻接缓存(`chain.adjacency` 的 skip_archived 过滤)必须**同点作废**——
|
|
285
|
+
# 此前只有本地写路径(add/_stage 尾三连)清缓存,set_state 后旧缓存会让
|
|
286
|
+
# 退役节点继续沿因果链/图面扩散(本批探针实证:归档后 causal_chain 仍含
|
|
287
|
+
# 目标)。懒导入避免模块环;`invalidate_cache` 内部自吞异常,不阻断推进。
|
|
288
|
+
from . import chain as _chain
|
|
289
|
+
_chain.invalidate_cache(cg)
|
|
283
290
|
try:
|
|
284
291
|
append_jsonl(os.path.join(cg.root, AUDIT_FILE), {
|
|
285
292
|
"t": at, "action": "set_state", "node_id": node_id,
|
package/md_cg/mcp_server.py
CHANGED
|
@@ -189,6 +189,9 @@ TOOLS = [
|
|
|
189
189
|
consistency=_p("boolean", "写入前节点间自动冲突检测(三级决策,默认 true)"),
|
|
190
190
|
on_conflict=_p("string", "冲突处置:reject(默认,抛错)|defer(不写)|record(记录放行)"),
|
|
191
191
|
condition_space=_p("object", "条件空间"),
|
|
192
|
+
spatial=_p("object", "3D 空间锚(0.8.0 身体×脑对接约定,四条裁定之二):"
|
|
193
|
+
"{\"coords3d\": {x,y,z}} 自定义键直存米制;"
|
|
194
|
+
"可选 bbox 投影后补(不改既有 bbox 2D 语义)"),
|
|
192
195
|
verification_basis=_p("string", "验证基底"),
|
|
193
196
|
non_applicable_conditions=_p("array", "不适用条件")),
|
|
194
197
|
},
|
|
@@ -1591,6 +1594,15 @@ def _split_ids(value):
|
|
|
1591
1594
|
return out
|
|
1592
1595
|
|
|
1593
1596
|
|
|
1597
|
+
# 生效条件:a 为 MCP 参数字典时——`a["spatial"]` 非 None 返回 {"spatial": ...},否则返回 {}
|
|
1598
|
+
# (**缺省不传**:未声明 spatial 的写入零落键,既有 fm 形态逐位不变;0.8.0 身体×脑对接
|
|
1599
|
+
# 四条裁定之二引入,gated 分支与非 gated add 两处共用本单点)。
|
|
1600
|
+
def _spatial_kw(a):
|
|
1601
|
+
if a.get("spatial") is not None:
|
|
1602
|
+
return {"spatial": a["spatial"]}
|
|
1603
|
+
return {}
|
|
1604
|
+
|
|
1605
|
+
|
|
1594
1606
|
# 生效条件:始终构造 ex(verify=a.get("verify")、importance=float(a["importance"]) 当 a.get("importance") is not None 否则 None、verification_basis=a.get("verification_basis") or (verdict or {}).get("basis")、non_applicable_conditions、role、derived_from=_split_ids(a.get("derived_from")) or None、relation),返回其中值不属于 (None, [], '', {}) 的键值对。
|
|
1595
1607
|
def _proposal_extras(a, verdict=None):
|
|
1596
1608
|
"""入队时保全 write 的落盘要素,避免裁决 accept 后退化成默认值。
|
|
@@ -3446,6 +3458,11 @@ def _dispatch(cg, name, args):
|
|
|
3446
3458
|
# A1 补接(2026-10-05):检验强度同族透传(remember_gated 内部
|
|
3447
3459
|
# add(**kw) 直达;此前声明在 gated 分支静默丢弃)。
|
|
3448
3460
|
check_strength=a.get("check_strength"),
|
|
3461
|
+
# 0.8.0 身体×脑对接(四条裁定之二,2026-10-06):`spatial`
|
|
3462
|
+
# 自定义键**直存**——约定 {"coords3d": {x,y,z}} 米制(可选 bbox
|
|
3463
|
+
# 投影后补);不改既有 fm.spatial.bbox 的 2D 语义。
|
|
3464
|
+
# 缺省不传(_spatial_kw)——未声明时零落键,既有 fm 形态逐位不变。
|
|
3465
|
+
**_spatial_kw(a),
|
|
3449
3466
|
non_applicable_conditions=a.get("non_applicable_conditions"),
|
|
3450
3467
|
importance_hint=hint, override=bool(a.get("override")),
|
|
3451
3468
|
consistency=bool(a.get("consistency", True)),
|
|
@@ -3498,6 +3515,9 @@ def _dispatch(cg, name, args):
|
|
|
3498
3515
|
# A1 补接(2026-10-05):检验强度入复现 meta——与
|
|
3499
3516
|
# writepipe._AUTONOMY_META_KEYS 同款(两条路径元数据等价)。
|
|
3500
3517
|
"check_strength",
|
|
3518
|
+
# 0.8.0 对接:spatial 同样随单落复现 meta(入队后
|
|
3519
|
+
# accept 落盘与直接落盘**元数据等价**,判据不分叉)。
|
|
3520
|
+
"spatial",
|
|
3501
3521
|
"role", "importance") if a.get(k) is not None}
|
|
3502
3522
|
# 直写面的裁决参数(本分支的 add 会自己跑冲突闸):随单落进复现 meta,
|
|
3503
3523
|
# 否则「确认后落盘」与「直接落盘」两条路径的判据不等价。
|
|
@@ -3526,6 +3546,8 @@ def _dispatch(cg, name, args):
|
|
|
3526
3546
|
verification_basis=a.get("verification_basis"),
|
|
3527
3547
|
# A1 补接(2026-10-05):检验强度透传(同 B2)。
|
|
3528
3548
|
check_strength=a.get("check_strength"),
|
|
3549
|
+
# 0.8.0 对接:spatial 直存透传(同 B2);缺省不传(零落键)。
|
|
3550
|
+
**_spatial_kw(a),
|
|
3529
3551
|
non_applicable_conditions=a.get("non_applicable_conditions"),
|
|
3530
3552
|
override=bool(a.get("override")),
|
|
3531
3553
|
consistency=bool(a.get("consistency", True)),
|
package/md_cg/mdcg.py
CHANGED
|
@@ -3690,7 +3690,12 @@ class MdCG:
|
|
|
3690
3690
|
# (非法视图 ValueError——fail-closed 只针对调用方误用)
|
|
3691
3691
|
and (view is None or roleviews.matches(e, view))
|
|
3692
3692
|
# 时效:只在显式启用时排除已过期(not_yet 保留)
|
|
3693
|
-
and not (validity and trust.is_expired(e, now=now))
|
|
3693
|
+
and not (validity and trust.is_expired(e, now=now))
|
|
3694
|
+
# 退役纪律(《秤》v2.1 §5.2;2026-10-06 接线):archived
|
|
3695
|
+
# 不进默认检索候选——判据单点 lifecycle.is_archived
|
|
3696
|
+
# (缺键=active fail-open),与生产路径 `MdCGOS._candidates`
|
|
3697
|
+
# 同一口径(此前基类面未接,属已登记的已知未覆盖面)。
|
|
3698
|
+
and not lifecycle.is_archived(e)):
|
|
3694
3699
|
entries.append(e)
|
|
3695
3700
|
|
|
3696
3701
|
# 默认关:索引里可能残留门控字段(曾开启过 / 回填过)→ 返回前剥离,
|
package/md_cg/scrub.py
CHANGED
|
@@ -115,11 +115,13 @@ def _access(cg):
|
|
|
115
115
|
return {}, {}
|
|
116
116
|
|
|
117
117
|
|
|
118
|
-
# 生效条件:`from . import chain` 成功且 chain.adjacency(cg) 正常返回时返回该 dict,导入或调用抛任何异常时返回 {}。
|
|
118
|
+
# 生效条件:`from . import chain` 成功且 chain.adjacency(cg, skip_archived=False) 正常返回时返回该 dict,导入或调用抛任何异常时返回 {}。
|
|
119
119
|
def _adjacency(cg) -> dict:
|
|
120
120
|
try:
|
|
121
121
|
from . import chain
|
|
122
|
-
|
|
122
|
+
# 维护面全量口径(2026-10-06 退役接线批次):去污染抽查是维护写面,
|
|
123
|
+
# 退役节点仍可被抽查 ⇒ 显式旁路退役过滤(度数口径与接线前逐位一致)。
|
|
124
|
+
return chain.adjacency(cg, skip_archived=False)
|
|
123
125
|
except Exception:
|
|
124
126
|
return {}
|
|
125
127
|
|