@furongjun1999/dsh-memory 0.7.0 → 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 +7 -6
- 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/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//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/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 +25 -25
- 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//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/consistency.py +133 -8
- package/md_cg/forgetting.py +38 -6
- package/md_cg/freshness.py +17 -1
- package/md_cg/lifecycle.py +30 -2
- package/md_cg/mcp_server.py +63 -7
- package/md_cg/mdcg.py +85 -17
- package/md_cg/mdcos.py +666 -16
- 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/stg.py +400 -50
- package/md_cg/stgidx.py +281 -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_cons200_scan_selection.py +791 -0
- 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_n214_n215_n221_n222_write_face_gates.py +7 -0
- package/md_cg/test_p2_mcp.py +8 -0
- 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_stgidx_index_parity.py +1020 -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/writepipe.py +101 -2
- package/package.json +1 -1
- package/skills/plugin.json +1 -1
package/md_cg/consistency.py
CHANGED
|
@@ -59,10 +59,21 @@ from .mdcg import expand_query_terms_weighted
|
|
|
59
59
|
LOG_FILE = "_consistency.jsonl"
|
|
60
60
|
|
|
61
61
|
MAX_SCAN = 200 # 单次检测最多比对的既有节点数(防 O(N) 爆炸)
|
|
62
|
+
# cons200(2026-10-03):本值是**相关面/保底面各自的限额**,不是
|
|
63
|
+
# 「索引序前 N」——数值一字未动(契约禁止面);超限走可观测
|
|
64
|
+
# (truncated/kept/hint),修法是选面与预筛,不是调大本值。
|
|
62
65
|
MAX_DEPTH = 3 # L2 递归深度上限(对齐 :273)
|
|
63
66
|
MAX_NODES = 60 # L2 递归展开节点上限(对齐 :273)
|
|
64
67
|
MIN_GAIN = 0.15 # 信息增益门槛:候选空间减少比例低于此值即停(对齐 :273)
|
|
65
68
|
|
|
69
|
+
# 截断可操作提示(cons200:与 stg `_CAP_HINT` 同口径精神——禁止静默)——文案必须
|
|
70
|
+
# 含「细化生效条件/不适用条件」与「建立条件索引」语义;数值放大(调 limit/MAX_SCAN)
|
|
71
|
+
# 不是修法,故明文劝阻。
|
|
72
|
+
_SCAN_HINT = ("候选 %s 条超过单次检测限额 limit=%s,已按相关性保留 %s 条"
|
|
73
|
+
"(截断不静默):请细化生效条件/不适用条件收窄候选集,"
|
|
74
|
+
"或按设计稿建立条件索引(docs/plans/stg条件化与结构索引_设计_v0.1.md)"
|
|
75
|
+
"——不要调大 limit/MAX_SCAN 数值。")
|
|
76
|
+
|
|
66
77
|
CLASH_HIGH = 0.6 # 条件互相覆盖阈值 → 明确互斥
|
|
67
78
|
CLASH_LOW = 0.35 # 条件部分覆盖阈值 → 待定
|
|
68
79
|
SELF_NEGATION = 0.5 # 自否定阈值:负条件被自身正文强命中
|
|
@@ -303,6 +314,53 @@ def _slot_text(content):
|
|
|
303
314
|
_ccg_field(content or "", "子功能"))
|
|
304
315
|
|
|
305
316
|
|
|
317
|
+
# --------------------------------------------------------------------------
|
|
318
|
+
# 选面支持件(cons200):相关性预筛打分(快照级,不读盘)
|
|
319
|
+
# --------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
# 生效条件:e 为索引快照条目、nid 为节点 id、tw_pos/tw_neg 为 {词: 权重}(可为空)、cov 为 _weighted_coverage 同签名函数、disc 为 _is_discipline 的预判结果(None 时按 `_is_discipline({"tags": e.get("tags")}, nid)` 现算)——只读条目内既有键(tags/rejection_terms),恒返回整数二元组 (rank, score):disc 真 → (3, 0);否则 rejection_terms 与 tw_pos/tw_neg 有整词子串交集 → (2, int(round(1e4 * max(双方 _cov)) ));仅 rejection_terms 非空 → (1, 0);其余 → (0, 0);不读盘、不解析正文、不调用 cg.get。
|
|
322
|
+
def _sift_score(e, nid, tw_pos, tw_neg, cov, disc=None):
|
|
323
|
+
"""候选相关性打分(**只排序,不裁决**)——选面的廉价预筛单点。
|
|
324
|
+
|
|
325
|
+
为什么需要它(cons200 = 设计稿签收点 5):修前选面是 `list(nodes.items())`
|
|
326
|
+
的索引序前 MAX_SCAN——库超限后 id 字典序靠后的记忆节点(`mem_*`)几乎从不
|
|
327
|
+
进入比对面;而在役库前 200 名基本全是 `code_*` 索引节点。修后按**相关性**
|
|
328
|
+
(本函数)排序取前 limit,让「可能与本内容构成条件冲突」的候选优先入面。
|
|
329
|
+
|
|
330
|
+
信号与廉价性(全部来自索引快照既有键,零读盘、零正文解析):
|
|
331
|
+
· `tags`(`_is_discipline` 单点判纪律)——纪律违反是硬冲突(REJECT),
|
|
332
|
+
且其命中走 `_ban_hit` 子串判定、不走词权,故纪律候选 rank 最高(3);
|
|
333
|
+
· `rejection_terms`(**不适用条件**的索引词项,由既有单点
|
|
334
|
+
`mdcg.rejection_index_terms` 生成——不新造第二套解析):与本次内容的
|
|
335
|
+
条件词面(tw_pos/tw_neg)做整词交集(`in` 子串,C 速度),命中即
|
|
336
|
+
rank 2 并以 `_weighted_coverage`(既有单点)给连续分;非空但无交集
|
|
337
|
+
rank 1(弱迹象:该节点确有负条件声明)。
|
|
338
|
+
诚实边界(不假装全知):本键只覆盖**负条件面**(新正↔旧负 / 新负↔旧负)。
|
|
339
|
+
「旧节点只声明生效条件(e_pos)」的比对面在快照里**无字段可判**(`_declared`
|
|
340
|
+
还要读 content 的 `# 生效条件:` 行)——故本函数不做任何剔除,只做排序;
|
|
341
|
+
信息不足的候选按 rank 0 落在相关面尾部、并在候选超限时由**保底面**
|
|
342
|
+
(修前扫描面 ∩ 候选,逐位保留)兜底,二者共同保证「修后检出 ⊇ 修前」。
|
|
343
|
+
"""
|
|
344
|
+
if disc is None:
|
|
345
|
+
disc = _is_discipline({"tags": e.get("tags")}, nid)
|
|
346
|
+
if disc:
|
|
347
|
+
return 3, 0
|
|
348
|
+
rt = e.get("rejection_terms") or []
|
|
349
|
+
if not rt:
|
|
350
|
+
return 0, 0
|
|
351
|
+
blob = " ".join(str(t) for t in rt)
|
|
352
|
+
hit = False
|
|
353
|
+
if tw_pos:
|
|
354
|
+
hit = any(str(t) in blob for t in tw_pos)
|
|
355
|
+
if not hit and tw_neg:
|
|
356
|
+
hit = any(str(t) in blob for t in tw_neg)
|
|
357
|
+
if not hit:
|
|
358
|
+
return 1, 0
|
|
359
|
+
s1 = cov(tw_pos, blob) if tw_pos else 0.0
|
|
360
|
+
s2 = cov(tw_neg, blob) if tw_neg else 0.0
|
|
361
|
+
return 2, int(round(10000 * max(s1, s2)))
|
|
362
|
+
|
|
363
|
+
|
|
306
364
|
# --------------------------------------------------------------------------
|
|
307
365
|
# L0 情绪通道(信息差二阶变化)
|
|
308
366
|
# --------------------------------------------------------------------------
|
|
@@ -426,7 +484,7 @@ def _recursive_reflect(cg, seeds, tw_pos, tw_neg, max_depth=MAX_DEPTH,
|
|
|
426
484
|
# 主入口:三级决策
|
|
427
485
|
# --------------------------------------------------------------------------
|
|
428
486
|
|
|
429
|
-
# 生效条件:以 cg.index.nodes 为既有节点、content(假值按 "")经 _new_terms 得 pos/neg 并算 tw_pos/tw_neg(non_applicable_conditions 入参先按 mdcos._is_null_condition 剔除空值语义哨兵——⑧
|
|
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;
|
|
430
488
|
def check(cg, content, layer=None, condition_space=None,
|
|
431
489
|
non_applicable_conditions=None, tags=None, exclude=None,
|
|
432
490
|
limit=MAX_SCAN, depth=MAX_DEPTH, auto_flywheel=False,
|
|
@@ -436,6 +494,7 @@ def check(cg, content, layer=None, condition_space=None,
|
|
|
436
494
|
返回完整判据(可审计):
|
|
437
495
|
verdict / reason / conflict_strength / emotional / conflicts[] /
|
|
438
496
|
recursion{} / missing[] / unresolved_id(若触发飞轮)
|
|
497
|
+
+ 选面读数 scanned / kept / truncated(超限时另附 hint)
|
|
439
498
|
|
|
440
499
|
verdict:
|
|
441
500
|
ACCEPT 无冲突,或新节点未声明条件(无从冲突)
|
|
@@ -443,6 +502,36 @@ def check(cg, content, layer=None, condition_space=None,
|
|
|
443
502
|
DEFER 条件互斥但可能可分辨(交给 L2 递归或飞轮)
|
|
444
503
|
BLINDSPOT 有条件声明,但既有节点全无声明 → 无法建立比对路径(不假装确定)
|
|
445
504
|
|
|
505
|
+
选面(cons200,2026-10-03;设计稿签收点 5;收口轮① 2026-10-03 复核 DEFER
|
|
506
|
+
后补面内例外):
|
|
507
|
+
修前=`list(nodes.items())` 索引序前 limit 并静默 break——在役库 17882
|
|
508
|
+
节点 ⇒ 检测面 ≈1.1%,且 id 字典序最前的 `code_*` 索引节点占满名额,
|
|
509
|
+
真正相关的记忆节点(`mem_*` 在字典序后段)几乎从不进入比对面。修后:
|
|
510
|
+
· 预筛(快照级、不读盘):`_sift_score` 只读条目既有键(tags /
|
|
511
|
+
rejection_terms),不做任何「信息不足」的剔除——唯一剔除档是
|
|
512
|
+
**可证无产出且不落修前扫描面**的「tw_pos 与 tw_neg 皆空(新节点
|
|
513
|
+
未声明可展开条件)时的非纪律候选」(c1/c2/c3/c4 与分歧判定都以
|
|
514
|
+
tw_* 为乘子;纪律违反走 `_ban_hit` 子串、不看词权,故纪律候选
|
|
515
|
+
一律保留)。**面内例外(收口轮①)**:剔除永不触及修前扫描面
|
|
516
|
+
(过滤后索引序前 limit 内的候选一律保留进候选面、落保底面通道)
|
|
517
|
+
——「修后检出 ⊇ 修前」由此是**与「条目 tags 与盘面 fm.tags
|
|
518
|
+
同源」无关的结构保证**(预筛读条目快照,而精比读盘面;非同源
|
|
519
|
+
形态下面内候选仍进精比面、由盘面判据裁决)。
|
|
520
|
+
· 精比面=相关面(`_sift_score` 序)前 limit ∪ **保底面**(过滤后
|
|
521
|
+
索引序前 limit ∩ 候选,逐位=修前扫描面)。保底面不撤是「修后检出
|
|
522
|
+
⊇ 修前」的结构性保证(不是概率);两面上限各 limit(≤2×MAX_SCAN)。
|
|
523
|
+
· 可观测:`scanned`=预筛后候选数、`kept`=实际精比数、`truncated`=
|
|
524
|
+
scanned>kept(截断不静默,附 `hint` 给细化条件/建索引方向)。
|
|
525
|
+
诚实边界:`rejection_terms` 只覆盖负条件面;「旧节点仅声明生效条件
|
|
526
|
+
(e_pos,正文 `# 生效条件:` 行)」在快照里无字段可判——此类节点在
|
|
527
|
+
候选超限且不落修前扫描面/保底面时可能不被精比(由 truncated 可观测)。
|
|
528
|
+
非同源形态(盘面 tags 与索引条目不一致,非合作写者直改 `.md` 不重建
|
|
529
|
+
索引)分两段:**面内**(修前扫描面内)由面内例外保底、精比读盘面判定,
|
|
530
|
+
修后仍检出(不构成回归);**面外残余**(条目未标纪律、盘面是纪律的
|
|
531
|
+
节点落修前扫描面之外)修前亦检不出——不构成回归,属已声明边界
|
|
532
|
+
(预筛读索引条目快照;协作写者协议下条目与盘面同源,无此形态)。
|
|
533
|
+
判据本身(阈值/覆盖计算/四态路由)一字未动——本批只改**选面与读数**。
|
|
534
|
+
|
|
446
535
|
non_applicable_conditions(⑧ 2026-09-30):入参里的**空值语义哨兵**
|
|
447
536
|
(`["无"]` / `["(无)"]` / `[""]`…)在本函数的入口统一剔除——判据复用
|
|
448
537
|
`mdcos._is_null_condition`(仓内唯一哨兵表,**不新造第二张词表**)。
|
|
@@ -475,7 +564,7 @@ def check(cg, content, layer=None, condition_space=None,
|
|
|
475
564
|
n_fn, n_sb = _slot_text(content)
|
|
476
565
|
|
|
477
566
|
conflicts, hard, divergences = [], [], []
|
|
478
|
-
strength,
|
|
567
|
+
strength, comparable = 0.0, 0
|
|
479
568
|
|
|
480
569
|
# ---- L1-a 自否定:自己的负条件排除自己的生效条件/正文 ----
|
|
481
570
|
body = _body_text(content)
|
|
@@ -491,16 +580,47 @@ def check(cg, content, layer=None, condition_space=None,
|
|
|
491
580
|
strength = max(strength, round(_cov(tw_neg, body), 4))
|
|
492
581
|
|
|
493
582
|
# ---- L1-b 与既有节点的条件级比对(反题) ----
|
|
583
|
+
# 选面(cons200):预筛(快照级,不读盘)→ 候选面 → 相关面 ∪ 保底面。
|
|
494
584
|
nodes = ((getattr(cg, "index", None) or {}).get("nodes") or {})
|
|
495
|
-
|
|
496
|
-
|
|
585
|
+
order = list(nodes.items()) # 索引序快照(H-4(a):快照后再迭代)
|
|
586
|
+
lim = max(0, int(limit))
|
|
587
|
+
no_terms = not (tw_pos or tw_neg) # 新节点未声明可展开条件
|
|
588
|
+
cand = [] # (rank, score, in_before, nid, e)
|
|
589
|
+
fidx = 0
|
|
590
|
+
for nid, e in order:
|
|
497
591
|
if exclude and nid == exclude:
|
|
498
592
|
continue
|
|
499
593
|
if layer and e.get("layer") != layer:
|
|
500
594
|
continue
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
595
|
+
in_before = fidx < lim # 是否在修前扫描面内(过滤后前 limit)
|
|
596
|
+
fidx += 1
|
|
597
|
+
disc = _is_discipline({"tags": e.get("tags")}, nid)
|
|
598
|
+
# 预筛唯一剔除档(可证无产出;其余一律放行——宁多勿漏):
|
|
599
|
+
# 新节点未声明可展开条件时,非纪律候选在精比中产出恒为零
|
|
600
|
+
# (c1/c2/c3/c4 与分歧判定均以 tw_pos/tw_neg 为乘子;纪律违反走
|
|
601
|
+
# _ban_hit 子串判定、不看词权——故纪律候选一律保留)。
|
|
602
|
+
# 收口轮①(复核 DEFER 后补):**面内例外**——剔除永不触及修前扫描面
|
|
603
|
+
# (过滤后索引序前 limit 内的候选一律保留),使「修后检出 ⊇ 修前」
|
|
604
|
+
# 成为与「条目 tags 与盘面 fm.tags 同源」无关的结构保证(非同源形态
|
|
605
|
+
# 下面内候选仍进精比面、由盘面判据裁决);面外非同源残余见 check
|
|
606
|
+
# docstring 诚实边界(修前亦检不出,不构成回归)。
|
|
607
|
+
if no_terms and not disc and not in_before:
|
|
608
|
+
continue
|
|
609
|
+
rank, score = _sift_score(e, nid, tw_pos, tw_neg, _cov, disc)
|
|
610
|
+
cand.append((rank, score, in_before, nid, e))
|
|
611
|
+
# 相关面:rank/score 降序 + id 升序终键(确定性;与索引物理序无关)
|
|
612
|
+
rel = sorted(cand, key=lambda t: (-t[0], -t[1], str(t[3])))[:lim]
|
|
613
|
+
rel_ids = {t[3] for t in rel}
|
|
614
|
+
# 精比面=相关面 ∪ 保底面(过滤后索引序前 limit ∩ 候选)——保底面即
|
|
615
|
+
# 修前扫描面;其逐位保留是「修后检出 ⊇ 修前」的结构性保证(非概率),
|
|
616
|
+
# 两面上限各 limit(≤ 2×MAX_SCAN;MAX_SCAN 数值未动)。
|
|
617
|
+
scan_face = [(t[3], t[4]) for t in rel]
|
|
618
|
+
scan_face += [(t[3], t[4]) for t in cand if t[2] and t[3] not in rel_ids]
|
|
619
|
+
scanned = len(cand) # 预筛后候选数(截断前)
|
|
620
|
+
kept = len(scan_face) # 实际精比数(相关面 ∪ 保底面)
|
|
621
|
+
truncated = scanned > kept
|
|
622
|
+
seeds = []
|
|
623
|
+
for nid, e in scan_face:
|
|
504
624
|
node = cg.get(nid) or {}
|
|
505
625
|
fm = node.get("frontmatter") or {}
|
|
506
626
|
body = node.get("content") or ""
|
|
@@ -630,9 +750,14 @@ def check(cg, content, layer=None, condition_space=None,
|
|
|
630
750
|
rec = {"t": time.time(), "layer": layer, "verdict": verdict,
|
|
631
751
|
"reason": reason, "conflict_strength": round(strength, 4),
|
|
632
752
|
"emotional": emo, "conflicts": allc, "recursion": recursion,
|
|
633
|
-
"missing": missing, "scanned": scanned, "
|
|
753
|
+
"missing": missing, "scanned": scanned, "kept": kept,
|
|
754
|
+
"truncated": truncated, "comparable": comparable,
|
|
634
755
|
"pos": pos[:6], "neg": neg[:6],
|
|
635
756
|
"actor": getattr(cg, "actor", "unknown")}
|
|
757
|
+
# 截断不静默(与 stg `_with_scan_reads` 同口径精神):候选有被限额丢弃时
|
|
758
|
+
# 附可操作 hint(细化条件 / 建索引方向;数值放大不是修法)。
|
|
759
|
+
if truncated:
|
|
760
|
+
rec["hint"] = _SCAN_HINT % (scanned, lim, kept)
|
|
636
761
|
|
|
637
762
|
# ---- 冲突自动触发飞轮(误差 → 补条件 → 结构更新) ----
|
|
638
763
|
# H11⑥(2026-09-30):**只对真冲突(REJECT)建单**。改前是
|
package/md_cg/forgetting.py
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
噪音,不该去强化既有记忆(否则例行日志
|
|
34
34
|
会把普通记忆刷成高重要性)
|
|
35
35
|
3) 冗余 ≥0.85 → MERGE ← 外部/未知来源的重复 = 又一次确认,强化
|
|
36
|
-
4) 半重复 且
|
|
36
|
+
4) 半重复 且 未触发保护 → DEFER ← 与重要度无关:半重复是「变更确认」的输入端
|
|
37
37
|
5) 重要度 ≥0.30 → ACCEPT
|
|
38
38
|
6) 新信息 ≥0.15 → ACCEPT
|
|
39
39
|
7) 其余 → DEFER
|
|
@@ -236,7 +236,7 @@ def importance_score(hint, novelty, kind, content):
|
|
|
236
236
|
return {"score": round(max(0.0, min(1.0, s)), 4), "from": "heuristic"}
|
|
237
237
|
|
|
238
238
|
|
|
239
|
-
# 生效条件:以 source_kind(role,verification_basis) 的 kind 与 redundancy(cg,content,layer=layer,exclude=node_id) 的 red["max"] 为输入,按 if/elif 顺序取首个命中分支——imp["score"]≥PROTECT_IMPORTANCE→"ACCEPT"
|
|
239
|
+
# 生效条件:以 source_kind(role,verification_basis) 的 kind 与 redundancy(cg,content,layer=layer,exclude=node_id) 的 red["max"] 为输入,按 if/elif 顺序取首个命中分支——imp["score"]≥PROTECT_IMPORTANCE→"ACCEPT"(reason 按 imp["from"] 分叉:hint→「触发不可遗忘保护」,heuristic→「未落保护——保护须显式声明」,issue50-d);否则 kind=="internal_deterministic" 且 red["max"]≥DUP_DROP→"DROP";否则 red["max"]≥DUP_MERGE→"MERGE";否则 red["max"]≥DUP_DROP→"DEFER"(**与 imp 无关**);否则 imp["score"]≥IMPORTANCE_MIN→"ACCEPT";否则 novelty≥NOVELTY_MIN→"ACCEPT";否则→"DEFER"。返回体附带 P-9b 可见性字段:entropy.overwrite_of / entropy.overwrite_ratio(同 id 覆写时非 null)、dedup_skipped(**仅 PROTECT 分支**非 null,标注「因保护优先未走去重」+ 去重判据读数)——两者都不改 verdict、不改落盘行为。
|
|
240
240
|
def assess(cg, content, layer="contextual", role=None, verification_basis=None,
|
|
241
241
|
importance_hint=None, node_id=None):
|
|
242
242
|
"""三问 → 四态裁决。返回完整判据(可审计,不只给结论)。
|
|
@@ -261,6 +261,27 @@ def assess(cg, content, layer="contextual", role=None, verification_basis=None,
|
|
|
261
261
|
关键正文并入他节点、检索归属漂移——风险高于收益;③ 缺陷本体是「绕过
|
|
262
262
|
去重而不可见」而非「保护存在」,标注即可消除静默且零回归面
|
|
263
263
|
(test_p9_forget_protect.py 的「importance_hint≥0.7→ACCEPT」逐字不变)。
|
|
264
|
+
|
|
265
|
+
issue50-d(2026-10-02):保护分支的 reason 文案按 **imp["from"] 分叉**——
|
|
266
|
+
改前启发式路径(不传 hint)过线时也声称「触发不可遗忘保护」,但落盘面
|
|
267
|
+
importance=0.5(默认)且无保护位(隔离沙箱直读 frontmatter 实证:
|
|
268
|
+
u_heur_new 裁决 0.837/落盘 0.5;t_heur 裁决 0.711/落盘 0.5)——审计面在
|
|
269
|
+
声称未发生的事。处置:from=="hint" 且 ≥0.7 维持原文案(add 的自动保护位
|
|
270
|
+
对显式声明真落 protected 位,文案与落盘一致);from=="heuristic" 且 ≥0.7
|
|
271
|
+
改为如实文案「启发式 …(未落保护——保护须显式声明)」。裁决面 verdict、
|
|
272
|
+
分支顺序、常量均不变;落盘口径的修复在 remember_gated ACCEPT 分支
|
|
273
|
+
(mdcos.py:落盘=裁决值 + importance_source 标注来源)。
|
|
274
|
+
|
|
275
|
+
issue50-a(2026-10-01):分支 ④「半重复 → DEFER」此前写作
|
|
276
|
+
`red["max"] >= DUP_DROP and imp["score"] < IMPORTANCE_MIN`,**结构性不可达**:
|
|
277
|
+
用户角色属外部惊奇来源(`SOURCE_WEIGHT["external_surprising"]=1.00`),启发式
|
|
278
|
+
重要度 = 0.5·novelty + 0.3·1.00 + 0.2·lf ⇒ 基线恰为 0.30 = IMPORTANCE_MIN,
|
|
279
|
+
且比较用 `>=`;插件侧还显式传 `hint=0.6`(同样 ≥0.30)。实测:重复度 0.7324 /
|
|
280
|
+
0.7606 的半重复内容一律走分支 ⑤ 判 ACCEPT,`_forgetting.jsonl` 的 DEFER 计数为
|
|
281
|
+
0(在役库 215 条留痕:ACCEPT 201 / MERGE 14)——本应是「变更确认」输入端的
|
|
282
|
+
那条回路从不产生输入。处置:**删去 `and imp["score"] < IMPORTANCE_MIN`**,
|
|
283
|
+
半重复(且未触发保护)一律 DEFER,与重要度解耦。分支顺序与其余六条分支的
|
|
284
|
+
判据/文案均不变,保护优先(①)仍最先,故保护面零回归。
|
|
264
285
|
"""
|
|
265
286
|
kind = source_kind(role, verification_basis)
|
|
266
287
|
red = redundancy(cg, content, layer=layer, exclude=node_id)
|
|
@@ -294,8 +315,19 @@ def assess(cg, content, layer="contextual", role=None, verification_basis=None,
|
|
|
294
315
|
if red["with"] and red["max"] >= DUP_MERGE:
|
|
295
316
|
due.append("与 %s 重复度 %.2f≥%.2f(够 MERGE 阈值)"
|
|
296
317
|
% (red["with"], red["max"], DUP_MERGE))
|
|
297
|
-
|
|
298
|
-
|
|
318
|
+
# issue50-d(2026-10-02):文案按来源分叉——`imp["from"]` 就在手上的
|
|
319
|
+
# imp 里,单点可判。显式 hint 维持「触发不可遗忘保护」(add 的自动
|
|
320
|
+
# 保护位会真落 protected 位,文案与落盘一致);启发式过线**只落分、
|
|
321
|
+
# 不打保护位**(issue50-d 裁决②:保护须显式声明),文案改成如实
|
|
322
|
+
# 表述——改前启发式 0.84 也声称「触发不可遗忘保护」,而落盘面
|
|
323
|
+
# importance=0.5 且无保护位(隔离沙箱实证),审计面在声称未发生的事。
|
|
324
|
+
# 分支顺序与 verdict 不变(硬边界:issue50-a 的那行除外,本批不动)。
|
|
325
|
+
if imp["from"] == "hint":
|
|
326
|
+
verdict, why = "ACCEPT", (f"重要度 {imp['score']:.2f}≥{PROTECT_IMPORTANCE}"
|
|
327
|
+
f"(触发不可遗忘保护)")
|
|
328
|
+
else:
|
|
329
|
+
verdict, why = "ACCEPT", (f"启发式 {imp['score']:.2f}≥{PROTECT_IMPORTANCE}"
|
|
330
|
+
f"(未落保护——保护须显式声明)")
|
|
299
331
|
dedup_skipped = {
|
|
300
332
|
"reason": "protect_importance",
|
|
301
333
|
"detail": ("保护优先分支排在冗余判定之前:本次未走 MERGE/DROP"
|
|
@@ -315,9 +347,9 @@ def assess(cg, content, layer="contextual", role=None, verification_basis=None,
|
|
|
315
347
|
elif red["max"] >= DUP_MERGE:
|
|
316
348
|
verdict, why = "MERGE", (f"重复度 {red['max']:.2f}≥{DUP_MERGE}"
|
|
317
349
|
f"(并入 {red['with']},强化既有)")
|
|
318
|
-
elif red["max"] >= DUP_DROP
|
|
350
|
+
elif red["max"] >= DUP_DROP:
|
|
319
351
|
verdict, why = "DEFER", (f"半重复 {red['max']:.2f}∈[{DUP_DROP},{DUP_MERGE})"
|
|
320
|
-
f"
|
|
352
|
+
f" 且未触发不可遗忘保护"
|
|
321
353
|
f"(待定复核)")
|
|
322
354
|
elif imp["score"] >= IMPORTANCE_MIN:
|
|
323
355
|
verdict, why = "ACCEPT", f"重要度 {imp['score']:.2f}≥{IMPORTANCE_MIN}"
|
package/md_cg/freshness.py
CHANGED
|
@@ -298,7 +298,7 @@ def entry_weight(entry, now=None, gamma=None, fm=None) -> tuple:
|
|
|
298
298
|
# 三件套:预演 / 逐节点留痕 / 反向 apply(与 weights.recalc 同款)
|
|
299
299
|
# --------------------------------------------------------------------------
|
|
300
300
|
|
|
301
|
-
# 生效条件:cg.index["nodes"] 为 dict 时按 layer 过滤、limit 为真值才截断;dry-run
|
|
301
|
+
# 生效条件:cg.index["nodes"] 为 dict 时按 layer 过滤、limit 为真值才截断;dry-run 只出报表(**不接档位闸**——零写盘面照旧);apply=True 时先经 autonomy_modes.e_gate() 档位判定(补强批次 v1.2·E 面接线,位置=任何盘面写入之前):非 ALLOW(plan 档无计划)即经 forbidden_result() fail-closed 早退(零写盘、零审计行、带 hint),ALLOW(confirm/full)则逐节点把乘子(round 6)写入 frontmatter.freshness_weight 与 freshness_components,乘子 < DEMOTE_BELOW 时另写 freshness_state="degraded"(**只标记,不删记录**),乘子下降且 demote_layer 给定时经 cg._move_layer(内含 protect.guard_move)搬迁;每条 append_jsonl(action="freshness") 后 rebuild_index;返回报表 dict;
|
|
302
302
|
def recalc(cg, layer=None, limit=None, apply=False, min_delta=APPLY_DELTA,
|
|
303
303
|
actor="maintain", dry_run_samples=10, now=None, demote_layer=None):
|
|
304
304
|
"""刷新/衰减权重的重算(**可预演 + 逐节点留痕 + 可回滚**)。
|
|
@@ -312,6 +312,12 @@ def recalc(cg, layer=None, limit=None, apply=False, min_delta=APPLY_DELTA,
|
|
|
312
312
|
`freshness_state="degraded"` 标记;要搬迁层位时给出 `demote_layer`,
|
|
313
313
|
走既有 `_move_layer`(其内已调 `protect.guard_move`,受保护节点需显式
|
|
314
314
|
`override`)——不新写第二条搬迁路径。
|
|
315
|
+
|
|
316
|
+
**档位闸(补强批次 v1.2·E 面接线)**:`apply=True` 的**写盘前**先经
|
|
317
|
+
`autonomy_modes.e_gate()`(设计 §三 plan 档 E「须命中计划步骤」)——
|
|
318
|
+
plan 档无计划 ⇒ `forbidden_result()` fail-closed 早退(零写盘、零审计行);
|
|
319
|
+
confirm/full ⇒ ALLOW,**原链原样**(逐位不变)。`apply=False`(预演)
|
|
320
|
+
不接闸:零写盘面不属「变更」。
|
|
315
321
|
"""
|
|
316
322
|
nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
|
|
317
323
|
ids = [nid for nid, e in list(nodes.items())
|
|
@@ -350,6 +356,16 @@ def recalc(cg, layer=None, limit=None, apply=False, min_delta=APPLY_DELTA,
|
|
|
350
356
|
samples.append(plan)
|
|
351
357
|
written, degraded = 0, 0
|
|
352
358
|
if apply:
|
|
359
|
+
# 三档自治(补强批次 v1.2·E 面接线,设计 §三 plan 档 E「须命中计划步骤」):
|
|
360
|
+
# E 类动作**写盘前**的档位判定单点。本函数无独立资格闸(逐节点取数在下方
|
|
361
|
+
# 循环里、取不到即 skip),故闸口取「进入写盘支路的第一行」=任何盘面写入
|
|
362
|
+
# 之前;放行分支即原链原样(confirm/full 逐位不变),plan 档无计划
|
|
363
|
+
# ⇒ fail-closed 早退,**零写盘、零 _maintain.jsonl 行**。
|
|
364
|
+
from . import autonomy_modes as _am
|
|
365
|
+
_dec = _am.e_gate()
|
|
366
|
+
if _dec["decision"] != _am.ALLOW:
|
|
367
|
+
return _am.forbidden_result(_dec, action="freshness", dry_run=False,
|
|
368
|
+
written=0, batch=batch)
|
|
353
369
|
if demote_layer:
|
|
354
370
|
bind_move_layer(cg) # 跨层降级走既有 _move_layer(现绑)
|
|
355
371
|
for plan in planned:
|
package/md_cg/lifecycle.py
CHANGED
|
@@ -202,7 +202,7 @@ def _sync_index(cg, node_id, fm) -> None:
|
|
|
202
202
|
|
|
203
203
|
# ---------------------------------------------------------------- 推进 / 回填
|
|
204
204
|
|
|
205
|
-
# 生效条件:cg.get(node_id) 为假值返回 {'ok': False, 'error': 'node_not_found', ...};否则以 state_of(fm) 作 src 调 stamp——stamp 不 ok 返回 ok=False + error=code,code == "noop" 返回 ok=True + changed=False
|
|
205
|
+
# 生效条件:cg.get(node_id) 为假值返回 {'ok': False, 'error': 'node_not_found', ...};否则以 state_of(fm) 作 src 调 stamp——stamp 不 ok 返回 ok=False + error=code,code == "noop" 返回 ok=True + changed=False;其余情况(合法且真迁移,即**资格面已过**)先经 autonomy_modes.e_gate() 档位判定(补强批次 v1.2·E 面接线,位置=写盘之前):非 ALLOW(plan 档无计划)即经 forbidden_result() fail-closed 早退(ok=False、error=autonomy_forbidden、零写盘、零审计行),ALLOW(confirm/full)才取 fm[HISTORY_FIELD][-1]["at"] 后写盘、_sync_index 并追加审计 JSONL(append_jsonl 抛异常被吞掉),返回 ok=True + changed=True。
|
|
206
206
|
def set_state(cg, node_id: str, dst: str, reason: str = None,
|
|
207
207
|
actor: str = None, override: bool = False) -> dict:
|
|
208
208
|
"""推进一个节点的生命周期状态(**唯一推进入口**)。
|
|
@@ -210,6 +210,13 @@ def set_state(cg, node_id: str, dst: str, reason: str = None,
|
|
|
210
210
|
非法迁移不走异常而是返回 `{"ok": False, "error": <code>, ...}`(负路由:
|
|
211
211
|
调用方自行决定降级处理;要硬拒请用 `require_transition`)。幂等迁移返回
|
|
212
212
|
`changed=False` 且不写盘。返回体始终带 `from` / `to` / `code` 便于审计。
|
|
213
|
+
|
|
214
|
+
**档位闸(补强批次 v1.2·E 面接线)**:合法且真迁移(`stamp` 已过=资格面
|
|
215
|
+
已过)之后、写盘之前,先经 `autonomy_modes.e_gate()`(设计 §三 plan 档
|
|
216
|
+
E「须命中计划步骤」)——plan 档无计划 ⇒ fail-closed 早退(
|
|
217
|
+
`ok=False`/`error="autonomy_forbidden"`/带 hint、零写盘、零审计行);
|
|
218
|
+
confirm/full ⇒ ALLOW、**原链原样**(逐位不变)。非法迁移与 noop 由
|
|
219
|
+
`stamp` 先行裁决、各回各的 code(档位**不参与**资格判定)。
|
|
213
220
|
"""
|
|
214
221
|
node = cg.get(node_id)
|
|
215
222
|
if not node:
|
|
@@ -222,6 +229,15 @@ def set_state(cg, node_id: str, dst: str, reason: str = None,
|
|
|
222
229
|
return {**base, "ok": False, "error": code, "reason": why}
|
|
223
230
|
if code == "noop":
|
|
224
231
|
return {**base, "ok": True, "changed": False, "reason": why}
|
|
232
|
+
# 三档自治(补强批次 v1.2·E 面接线,设计 §三 plan 档 E「须命中计划步骤」):
|
|
233
|
+
# 位置=资格面(`stamp` 的合法性/保护裁决)之后、写盘之前——非法迁移与 noop
|
|
234
|
+
# 已在上方各回各的 code,档位不参与资格判定;confirm/full ⇒ ALLOW 原链原样。
|
|
235
|
+
from . import autonomy_modes as _am
|
|
236
|
+
_dec = _am.e_gate()
|
|
237
|
+
if _dec["decision"] != _am.ALLOW:
|
|
238
|
+
out = _am.forbidden_result(_dec, node_id=node_id, changed=False)
|
|
239
|
+
out.update({"from": src, "to": dst, "code": "autonomy_forbidden"})
|
|
240
|
+
return out
|
|
225
241
|
at = fm[HISTORY_FIELD][-1]["at"]
|
|
226
242
|
cg._write_node(node_id, os.path.join(cg.root, node["path"]), fm,
|
|
227
243
|
node.get("content") or "")
|
|
@@ -236,13 +252,18 @@ def set_state(cg, node_id: str, dst: str, reason: str = None,
|
|
|
236
252
|
return {**base, "ok": True, "changed": True, "reason": why, "at": at}
|
|
237
253
|
|
|
238
254
|
|
|
239
|
-
# 生效条件:nodes 取 (cg.index or {}).get("nodes") or {},遍历中缺 STATE_FIELD 或该值不在 STATES 的 nid 计入 missing(累计数达 limit 即 break,limit <= 0 时立即 break 使 missing 为空),apply 假值只返回 dry_run=True
|
|
255
|
+
# 生效条件:nodes 取 (cg.index or {}).get("nodes") or {},遍历中缺 STATE_FIELD 或该值不在 STATES 的 nid 计入 missing(累计数达 limit 即 break,limit <= 0 时立即 break 使 missing 为空),apply 假值只返回 dry_run=True 的盘点结果(**不接档位闸**——零写盘面照旧),apply 为真时先经 autonomy_modes.e_gate() 档位判定(补强批次 v1.2·E 面接线,位置=盘点资格面之后、写盘之前):非 ALLOW(plan 档无计划)即经 forbidden_result() fail-closed 早退(零写盘、backfilled=0),ALLOW(confirm/full)才对每个 missing 中 cg.get 取不到的记入 failed、取到的把 state_of(fm) 显式写回 fm[STATE_FIELD] 并写盘与 _sync_index 后记入 done,返回 dry_run=False 的结果。
|
|
240
256
|
def backfill(cg, apply: bool = False, limit: int = 5000) -> dict:
|
|
241
257
|
"""存量回填:把缺 `state` 的节点显式补成 `active`(幂等)。
|
|
242
258
|
|
|
243
259
|
缺字段本就按 active 解释(`state_of`),故回填**不是让功能工作的前提**,
|
|
244
260
|
而是把「缺省」变成「显式」——索引/审计里从此可直接读到状态。
|
|
245
261
|
`apply=False`(默认)只盘点。写不动(文件取不回)的节点进 `failed` 如实上报。
|
|
262
|
+
|
|
263
|
+
**档位闸(补强批次 v1.2·E 面接线)**:`apply=True` 的**写盘前**先经
|
|
264
|
+
`autonomy_modes.e_gate()`(设计 §三 plan 档 E「须命中计划步骤」)——plan 档
|
|
265
|
+
无计划 ⇒ fail-closed 早退(零写盘、`backfilled=0`);confirm/full ⇒ ALLOW、
|
|
266
|
+
原链原样。`apply=False`(盘点)不接闸。
|
|
246
267
|
"""
|
|
247
268
|
nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
|
|
248
269
|
missing = []
|
|
@@ -256,6 +277,13 @@ def backfill(cg, apply: bool = False, limit: int = 5000) -> dict:
|
|
|
256
277
|
if not apply:
|
|
257
278
|
return {"ok": True, "dry_run": True, "scanned": len(nodes),
|
|
258
279
|
"missing": len(missing), "planned": missing[:50]}
|
|
280
|
+
# 三档自治(补强批次 v1.2·E 面接线):E 类动作**写盘前**的档位判定单点
|
|
281
|
+
# (同 `set_state` 口径)——盘点(资格面)已完成、任何盘面写入之前。
|
|
282
|
+
from . import autonomy_modes as _am
|
|
283
|
+
_dec = _am.e_gate()
|
|
284
|
+
if _dec["decision"] != _am.ALLOW:
|
|
285
|
+
return _am.forbidden_result(_dec, dry_run=False, scanned=len(nodes),
|
|
286
|
+
missing=len(missing), backfilled=0)
|
|
259
287
|
done, failed = [], []
|
|
260
288
|
for nid in missing:
|
|
261
289
|
node = cg.get(nid)
|
package/md_cg/mcp_server.py
CHANGED
|
@@ -2071,7 +2071,7 @@ def _task_call(cg, a):
|
|
|
2071
2071
|
return _t.list_tasks(cg, status=tstat or None, limit=a.get("limit"))
|
|
2072
2072
|
|
|
2073
2073
|
|
|
2074
|
-
# 生效条件:op 取 a.get("op") 转 str 去空白并 lower;缺失时按参数签名回推(含 content→write / query|node_id→read / intent→route;无签名可推时维持 read),op=="help" 在角色闸门之前直通 _help_call;其余 op 先经 cg.principal.require_op(op)(越权即 AccessDenied)再按 alias→内联名→cli 表分派;未识别 op 抛 ValueError;
|
|
2074
|
+
# 生效条件:op 取 a.get("op") 转 str 去空白并 lower;缺失时按参数签名回推(含 content→write / query|node_id→read / intent→route;无签名可推时维持 read),op=="help" 在角色闸门之前直通 _help_call;其余 op 先经 cg.principal.require_op(op)(越权即 AccessDenied)再按 alias→内联名→cli 表分派;未识别 op 抛 ValueError;fork:op=="forget" 的**删除**分支走 cg.forget_gated(三档自治批次②:档位判定在既有资格闸之后,变更确认档出变更单且不软删;restore 分支不变),op=="write" 经 writepipe 默认链(链尾 autonomy 档位闸);
|
|
2075
2075
|
def _cg_dispatch(cg, a):
|
|
2076
2076
|
"""认知图唯一入口的 op 分发主体。"""
|
|
2077
2077
|
op = (a.get("op") or "read").strip().lower()
|
|
@@ -2408,8 +2408,11 @@ def _cg_dispatch(cg, a):
|
|
|
2408
2408
|
if op == "forget":
|
|
2409
2409
|
if (a.get("action") or "forget").strip().lower() == "restore":
|
|
2410
2410
|
return cg.restore(a.get("node_id", ""), force=bool(a.get("force")))
|
|
2411
|
-
|
|
2412
|
-
|
|
2411
|
+
# 三档自治批次②(设计 §三 D 删除):工具面经 forget_gated——档位判定在
|
|
2412
|
+
# 既有资格闸(not_found / protect.guard_forget)**之后**,变更确认档
|
|
2413
|
+
# 出变更单且不软删;库层 forget 语义不变(脚本通道不接,本批边界)。
|
|
2414
|
+
return cg.forget_gated(a.get("node_id", ""), a.get("reason", ""),
|
|
2415
|
+
override=bool(a.get("override")))
|
|
2413
2416
|
|
|
2414
2417
|
if op == "protect":
|
|
2415
2418
|
return _protect_call(cg, a)
|
|
@@ -3124,7 +3127,7 @@ def _whitebox_call(cg, a):
|
|
|
3124
3127
|
return whitebox.dispatch(cg, a)
|
|
3125
3128
|
|
|
3126
3129
|
|
|
3127
|
-
# 生效条件:op=(a.get("op") or "").strip().lower();op=="relation" 时返回 stg.relation(cg, a.get("a",""), a.get("b",""), time_axis=axis);op=="timeline" 时返回 stg.timeline(cg, layer=a.get("layer"), limit=int(a.get("limit") or 50), desc=bool(a.get("desc", True)), time_axis=axis, session=a.get("session"))(session 缺省不过滤读遍所有会话,"*" = 显式跨会话,其它值 = 本会话视图);op=="anchors" 时返回 stg.anchors(cg, time_window=a.get("time_window"), bbox=a.get("bbox"), layer=a.get("layer"), limit=int(a.get("limit") or 50));op=="consistency" 时返回 stg.consistency(cg, layer=a.get("layer"), limit=int(a.get("limit") or 50));op 为空或其它的值抛 ValueError
|
|
3130
|
+
# 生效条件:op=(a.get("op") or "").strip().lower();op=="relation" 时返回 stg.relation(cg, a.get("a",""), a.get("b",""), time_axis=axis);op=="timeline" 时返回 stg.timeline(cg, layer=a.get("layer"), limit=int(a.get("limit") or 50), desc=bool(a.get("desc", True)), time_axis=axis, session=a.get("session"))(session 缺省不过滤读遍所有会话,"*" = 显式跨会话,其它值 = 本会话视图);op=="anchors" 时返回 stg.anchors(cg, time_window=a.get("time_window"), bbox=a.get("bbox"), layer=a.get("layer"), limit=int(a.get("limit") or 50));op=="consistency" 时返回 stg.consistency(cg, layer=a.get("layer"), limit=int(a.get("limit") or 50));op 为空或其它的值抛 ValueError;返回体**原样透传**——stg 三接口的 truncated/scanned/kept/hint 读数与修正后的 count 语义(条件命中总数,issue #52)不经本层裁剪或改写,max_scan 本批不开显式入参。
|
|
3128
3131
|
def _stg_call(cg, a):
|
|
3129
3132
|
"""语义时空图唯一入口。
|
|
3130
3133
|
|
|
@@ -3133,6 +3136,13 @@ def _stg_call(cg, a):
|
|
|
3133
3136
|
的透传策略**相反**——`cg` 侧缺省由库层定(不启用的时间算子),`stg` 侧轴
|
|
3134
3137
|
决定「排序/关系依据」必须**恒有值**,故在此填默认;非法轴仍由
|
|
3135
3138
|
`trust.time_axis_of` fail-closed 抛错(本层不预先白名单,避免两套枚举)。
|
|
3139
|
+
|
|
3140
|
+
返回体**原样透传**(issue #52):timeline/anchors/consistency 新增的
|
|
3141
|
+
`truncated` / `scanned` / `kept` 读数与截断时的 `hint` 随返回值自动带出,
|
|
3142
|
+
本层不裁剪、不改写;`count` 语义已是**条件命中总数**(不再受索引切片影响,
|
|
3143
|
+
在役对照 `timeline(session=…)` 由 25 恢复为 229)。`max_scan` 本批不在
|
|
3144
|
+
MCP 面开显式入参(契约:数值不是修法——需要收窄时细化生效条件/
|
|
3145
|
+
不适用条件,或按设计稿建立条件索引)。
|
|
3136
3146
|
"""
|
|
3137
3147
|
from . import stg
|
|
3138
3148
|
op = (a.get("op") or "").strip().lower()
|
|
@@ -3287,7 +3297,7 @@ def recall_fusion_default(use_fuzzy, use_semantic, use_goal, use_causal,
|
|
|
3287
3297
|
else None)
|
|
3288
3298
|
|
|
3289
3299
|
|
|
3290
|
-
# 生效条件:name 为已注册工具名之一(cg / stg / mdcg_whitebox / mdcg_service_info / mdcg_remember 等);name 属 _MDCG_OP_REQUIRE 且 cg.principal 具 require_op 属性时先 require_op(越权抛 AccessDenied),principal 为 None 或无该方法时跳过;cg 走 _cg_call、stg 走 _stg_call、whitebox 走 _whitebox_call;未识别的 name 返回含 error 的响应字典而不抛异常,进程不因此中断;
|
|
3300
|
+
# 生效条件:name 为已注册工具名之一(cg / stg / mdcg_whitebox / mdcg_service_info / mdcg_remember 等);name 属 _MDCG_OP_REQUIRE 且 cg.principal 具 require_op 属性时先 require_op(越权抛 AccessDenied),principal 为 None 或无该方法时跳过;cg 走 _cg_call、stg 走 _stg_call、whitebox 走 _whitebox_call;未识别的 name 返回含 error 的响应字典而不抛异常,进程不因此中断;mdcg_forget 走 cg.forget_gated(同 cg(op=forget) 的档位路径)、mdcg_remember 的非 gated 直写分支在同 id 覆写时先过 cg.write_qualify 再按档位判定(三档自治批次②);
|
|
3291
3301
|
def _dispatch(cg, name, args):
|
|
3292
3302
|
a = args or {}
|
|
3293
3303
|
# P1-1:mdcg_* 面的角色作用域闸(与 cg 工具的 require_op 同一语义——
|
|
@@ -3354,6 +3364,51 @@ def _dispatch(cg, name, args):
|
|
|
3354
3364
|
_prior_cov = (_forgetting.self_coverage(cg, _prior, _content)
|
|
3355
3365
|
if _prior is not None else None)
|
|
3356
3366
|
_dup = _forgetting.redundancy(cg, _content, layer=_layer, exclude=nid)
|
|
3367
|
+
# 三档自治批次②(设计 §三):本分支是**热写入路径的第二落点**(另两处 =
|
|
3368
|
+
# writepipe 链尾执行器、remember_gated)——同 id 已存在 ⇒ C 改写、
|
|
3369
|
+
# 不存在 ⇒ A 新增,判据复用 forgetting.prior_node(不另写一份存在性判据)。
|
|
3370
|
+
# 位置:既有 `if a.get("gated")` 分支之后、`cg.add` 之前(直写面没有别的
|
|
3371
|
+
# 资格闸;audit/限流属 gated 分支的链)。确认档 ⇒ 出单且不落盘。
|
|
3372
|
+
from . import autonomy_modes as _am
|
|
3373
|
+
_action = _am.C_REWRITE if _prior is not None else _am.A_ADD
|
|
3374
|
+
if _prior is not None:
|
|
3375
|
+
# 资格在先(纯加严):见 writepipe._gate_autonomy 同款注释——覆写的
|
|
3376
|
+
# 层闸/写保护闸在 cg.add 内部,档位闸不得抢在它们之前把拒绝变成入队。
|
|
3377
|
+
cg.write_qualify(nid, target_layer=_layer,
|
|
3378
|
+
override=bool(a.get("override")),
|
|
3379
|
+
actor=getattr(cg, "actor", None))
|
|
3380
|
+
_dec = _am.decide(_action)
|
|
3381
|
+
if _dec["decision"] != _am.ALLOW:
|
|
3382
|
+
_aut = {"mode": _dec["mode"], "action": _dec["action"],
|
|
3383
|
+
"action_name": _dec["action_name"],
|
|
3384
|
+
"decision": _dec["decision"]}
|
|
3385
|
+
if _dec["decision"] == _am.FORBID:
|
|
3386
|
+
return {"ok": False, "id": nid, "committed": False,
|
|
3387
|
+
"moved_to": "autonomy_forbidden",
|
|
3388
|
+
"error": "autonomy_forbid", "autonomy": _aut,
|
|
3389
|
+
"hint": _dec["hint"]}
|
|
3390
|
+
_meta = {k: a[k] for k in
|
|
3391
|
+
("tags", "condition_space", "verification_basis",
|
|
3392
|
+
"non_applicable_conditions", "derived_from", "relation",
|
|
3393
|
+
"role", "importance") if a.get(k) is not None}
|
|
3394
|
+
# 直写面的裁决参数(本分支的 add 会自己跑冲突闸):随单落进复现 meta,
|
|
3395
|
+
# 否则「确认后落盘」与「直接落盘」两条路径的判据不等价。
|
|
3396
|
+
_meta["consistency"] = bool(a.get("consistency", True))
|
|
3397
|
+
_meta["on_conflict"] = a.get("on_conflict") or "reject"
|
|
3398
|
+
_meta["override"] = bool(a.get("override"))
|
|
3399
|
+
_pay = _am.mutation_payload(
|
|
3400
|
+
_action, nid, after=_content,
|
|
3401
|
+
reason="mdcg_remember 直写(%s)" % _am.ACTION_NAMES[_action],
|
|
3402
|
+
primitive="add", meta=_meta)
|
|
3403
|
+
_pr = _am.propose_mutation(cg, _action, nid, payload=_pay,
|
|
3404
|
+
layer=_layer,
|
|
3405
|
+
sensitivity=a.get("sensitivity"),
|
|
3406
|
+
info=True)
|
|
3407
|
+
return {"ok": True, "id": nid, "committed": False,
|
|
3408
|
+
"moved_to": "review_queue", "pid": _pr["pid"],
|
|
3409
|
+
"mutation": _pay, "autonomy": _aut,
|
|
3410
|
+
"hint": "%s 本次未落盘:变更单已入审核队列(pid=%s,目标 %s)。"
|
|
3411
|
+
% (_dec["hint"], _pr["pid"], nid)}
|
|
3357
3412
|
written = cg.add(nid, _content, layer=_layer,
|
|
3358
3413
|
# B2:同上——落盘面丢字段=上游声明静默失效。
|
|
3359
3414
|
sensitivity=a.get("sensitivity"),
|
|
@@ -3524,8 +3579,9 @@ def _dispatch(cg, name, args):
|
|
|
3524
3579
|
return {"records": cg.review_records(pid=a.get("pid"))}
|
|
3525
3580
|
|
|
3526
3581
|
if name == "mdcg_forget":
|
|
3527
|
-
|
|
3528
|
-
|
|
3582
|
+
# 三档自治批次②:与 cg(op=forget) 同一档位路径(forget_gated,见该分支注释)。
|
|
3583
|
+
return cg.forget_gated(a.get("node_id", ""), a.get("reason", ""),
|
|
3584
|
+
override=bool(a.get("override")))
|
|
3529
3585
|
|
|
3530
3586
|
if name == "mdcg_restore":
|
|
3531
3587
|
return cg.restore(a.get("node_id", ""), force=bool(a.get("force")))
|