@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/mdcg.py
CHANGED
|
@@ -1451,6 +1451,12 @@ class MdCG:
|
|
|
1451
1451
|
# (自身写入已由 _stage 落进内存索引,计入签名会让每次自身 flush 都被
|
|
1452
1452
|
# 判成「他进程变化」→ 自身写入触发全量自重载)。flush() 里登记。
|
|
1453
1453
|
self._own_shard = None
|
|
1454
|
+
# 第 3 层结构索引(设计稿 §3.2):装载期内存倒排,**首次 stg 访问时**才构建
|
|
1455
|
+
# (惰性);写路径经 `_set_index_entry`/`_remove_index_entry` 同钩增量维护,
|
|
1456
|
+
# 整体换索引的路径(重载/compact/rebuild)经 `_invalidate_stg_index` 失效后
|
|
1457
|
+
# 由下次访问重建。占用面:1.7 万节点 ≈ 三张小表 + 位序/分类两表(内存派生,
|
|
1458
|
+
# 不改磁盘格式)。
|
|
1459
|
+
self._stg_index = None
|
|
1454
1460
|
self.index = self._load_index()
|
|
1455
1461
|
# P1b-2(2026-09-26,DSH 在役复验):索引只在本行装载一次,此后
|
|
1456
1462
|
# read/get/search 全走内存态——其它进程(review_cli autoflush=1)
|
|
@@ -1671,6 +1677,10 @@ class MdCG:
|
|
|
1671
1677
|
self._dirty_replay_superseded = superseded
|
|
1672
1678
|
idx["buckets"] = self._count_buckets(idx["nodes"])
|
|
1673
1679
|
self.index = idx
|
|
1680
|
+
# 第 3 层结构索引随整体换索引一并失效:重载取回的是**另一份**快照
|
|
1681
|
+
# (他进程写的新记录/新盘面),旧表按身份判代际必然不符——显式丢表,
|
|
1682
|
+
# 下次 stg 访问按新快照重建(惰性失效重建,与 rebuild/compact 同路)。
|
|
1683
|
+
self._invalidate_stg_index()
|
|
1674
1684
|
self._index_sig = sig
|
|
1675
1685
|
# 旧索引代里的派生缓存一并失效:query 结果缓存(hotcache,默认关)
|
|
1676
1686
|
# 缓存的是旧候选池上的结果;解析读缓存(readcache,默认开)按 path
|
|
@@ -1785,13 +1795,20 @@ class MdCG:
|
|
|
1785
1795
|
atomic_write(self.index_path, json.dumps(idx, ensure_ascii=False))
|
|
1786
1796
|
ShardedLog.clear(self.index_log_dir)
|
|
1787
1797
|
self.index = idx
|
|
1798
|
+
# 第 3 层结构索引随快照替换失效(快照重建过,旧表与它的划分关系不再
|
|
1799
|
+
# 有据可依):下次 stg 访问重建;不在此处立即重算——compact 是收尾路
|
|
1800
|
+
# 径,重建成本挪到真正需要索引的那次读(惰性失效重建,同一选)。
|
|
1801
|
+
self._invalidate_stg_index()
|
|
1788
1802
|
# P1b-2:自己写了快照 → 主动刷新签名,防止后续读路径把自己的
|
|
1789
1803
|
# compact 误判为「他进程写入」而做一次无谓重载(自写自载循环)。
|
|
1790
1804
|
self._index_sig = self._index_signature()
|
|
1791
1805
|
return idx
|
|
1792
1806
|
|
|
1793
|
-
# 生效条件:self._dirty 非空时才在 FileLock(index_path) 下(必要时新建 ShardedLog)逐条 append、清空 _dirty 并关闭分片句柄;self._dirty
|
|
1807
|
+
# 生效条件:self._dirty 非空时才在 FileLock(index_path) 下(必要时新建 ShardedLog)逐条 append、清空 _dirty 并关闭分片句柄;self._dirty 为空时立即返回、不写任何记录;**不动 self.index、不失效第 3 层结构索引**——flush 只落索引派生物(分片日志),内存快照与其划分表本就不变(「flush 后表与快照一致」由守卫钉住);
|
|
1794
1808
|
def flush(self):
|
|
1809
|
+
# 第 3 层结构索引(stgidx):flush **不失效**——它不改内存索引(记录早在
|
|
1810
|
+
# `_stage` 时就已同钩进表),若每次 flush 都丢表,惰性重建会在写读交替
|
|
1811
|
+
# 负载下每轮重扫(与 readcache「写谁失效谁」同款纪律)。
|
|
1795
1812
|
if not self._dirty:
|
|
1796
1813
|
return
|
|
1797
1814
|
# 世代/互斥防线(批次9,M3.3 落地):append 必须与 compact/rebuild 的
|
|
@@ -1843,7 +1860,7 @@ class MdCG:
|
|
|
1843
1860
|
self._log.close()
|
|
1844
1861
|
self._log = None
|
|
1845
1862
|
|
|
1846
|
-
# 生效条件:以节点文件绝对路径 p、其所在层目录名 layer、解析出的 fm 与 content 为入参,构造含 path(相对 root 正斜杠)/layer(fm 回落 layer)/role/content_kind/session/tags/bucket(父目录名,等于层名时 None)/bucket_zh/importance/created_at/verification_basis/has_neg_conditions/content_hash/temporal/spatial/time_window/lifecycle 与 trust 状态字段/branch_id/branched_from/evidence_count/big_domain/observation_position/subgraph/edges/protected/protection_reason/immutable/self_state/derived_from/derived_relation 各键的条目,经 _strip_empty_gate_fields 清洗后返回;
|
|
1863
|
+
# 生效条件:以节点文件绝对路径 p、其所在层目录名 layer、解析出的 fm 与 content 为入参,构造含 path(相对 root 正斜杠)/layer(fm 回落 layer)/role/content_kind/session/tags/bucket(父目录名,等于层名时 None)/bucket_zh/importance/importance_source(issue50-d:fm 键缺省 None)/created_at/verification_basis/has_neg_conditions/content_hash/temporal/spatial/time_window/lifecycle 与 trust 状态字段/branch_id/branched_from/evidence_count/big_domain/observation_position/subgraph/edges/protected/protection_reason/immutable/self_state/derived_from/derived_relation 各键的条目,经 _strip_empty_gate_fields 清洗后返回;
|
|
1847
1864
|
def _node_entry(self, p, layer, fm, content):
|
|
1848
1865
|
"""单节点索引条目——_scan_nodes 与定向 upsert 共用的**唯一真源**。
|
|
1849
1866
|
|
|
@@ -1875,6 +1892,10 @@ class MdCG:
|
|
|
1875
1892
|
# S1b 跨语言收敛免读文件可判。
|
|
1876
1893
|
"bucket_zh": fm.get("bucket_zh") or None,
|
|
1877
1894
|
"importance": fm.get("importance", 0.5),
|
|
1895
|
+
# issue50-d(2026-10-02):重要度**来源**入快照("hint"|"heuristic",
|
|
1896
|
+
# 旧库无此键 → None)——「这条 0.8 是显式声明还是启发式分」免读
|
|
1897
|
+
# 文件可判(与既有扁平键同理由:检索期/审计期不读盘)。
|
|
1898
|
+
"importance_source": fm.get("importance_source"),
|
|
1878
1899
|
"created_at": fm.get("created_at", 0),
|
|
1879
1900
|
"verification_basis": fm.get("verification_basis"),
|
|
1880
1901
|
"has_neg_conditions": nodefile.has_non_applicable(content),
|
|
@@ -1979,6 +2000,10 @@ class MdCG:
|
|
|
1979
2000
|
atomic_write(self.index_path, json.dumps(idx, ensure_ascii=False))
|
|
1980
2001
|
ShardedLog.clear(self.index_log_dir)
|
|
1981
2002
|
self.index = idx
|
|
2003
|
+
# 第 3 层结构索引随全量重建失效(新快照 = 新盘面语义):下次 stg 访问
|
|
2004
|
+
# 重建——重建后的划分不变量由守卫钉住(写/删/rebuild/compact 后三表与
|
|
2005
|
+
# 快照一致)。
|
|
2006
|
+
self._invalidate_stg_index()
|
|
1982
2007
|
# P1b-2:rebuild 同 compact——写完快照即刷新签名(自写不自载)。
|
|
1983
2008
|
self._index_sig = self._index_signature()
|
|
1984
2009
|
# broad=True:rebuild 以盘面扫描为准——ccgc/crosscheck/backfill 等
|
|
@@ -2046,7 +2071,8 @@ class MdCG:
|
|
|
2046
2071
|
derived_from=None, relation: str = provenance.DEFAULT_RELATION,
|
|
2047
2072
|
semantic: str = None, depends_on=None, valid_from=None,
|
|
2048
2073
|
valid_until=None, effective_from=None, effective_until=None,
|
|
2049
|
-
believed_at=None, verification_state: str = None,
|
|
2074
|
+
believed_at=None, verification_state: str = None,
|
|
2075
|
+
importance_source: str = None, **extra) -> str:
|
|
2050
2076
|
"""写入一个节点。
|
|
2051
2077
|
|
|
2052
2078
|
verification_basis: 外部验证基底(白箱信任的硬门槛),
|
|
@@ -2236,6 +2262,11 @@ class MdCG:
|
|
|
2236
2262
|
"evidence_count": 0, "positive_evidence": 0, "negative_evidence": 0,
|
|
2237
2263
|
}
|
|
2238
2264
|
fm.update(extra)
|
|
2265
|
+
# issue50-d(2026-10-02):重要度**来源**声明("hint"|"heuristic")落
|
|
2266
|
+
# frontmatter——下游免读文件即可判「这条 0.8 是显式声明还是闸门启发式
|
|
2267
|
+
# 分」。缺省 None 不落键(既有调用方 fm 形态逐位不变,零回归)。
|
|
2268
|
+
if importance_source:
|
|
2269
|
+
fm["importance_source"] = importance_source
|
|
2239
2270
|
if bucket_zh:
|
|
2240
2271
|
fm["bucket_zh"] = bucket_zh
|
|
2241
2272
|
# S1 大域先验:写入时固化「内容 → 大域」(契约 §3 S1)。
|
|
@@ -2435,8 +2466,14 @@ class MdCG:
|
|
|
2435
2466
|
fm["derived_from"] = parents
|
|
2436
2467
|
fm["derived_relation"] = derived_rel
|
|
2437
2468
|
# 重要性 ≥0.7 自动打保护标记(对齐 tool_table:≥0.7 触发不可遗忘保护)
|
|
2469
|
+
# issue50-d(2026-10-02):自动保护只认**显式声明**——调用方随 add
|
|
2470
|
+
# 下传 importance_source 时,"heuristic"(闸门启发式分)过线**只落分、
|
|
2471
|
+
# 不打位**(remember_gated 的裁决面文案已如实说「未落保护」);
|
|
2472
|
+
# importance_source 缺省 None(add 的全部既有调用方都不传)⇒ 本条件
|
|
2473
|
+
# 恒真、既有行为逐位不变,零回归由构造保证。
|
|
2438
2474
|
if (float(importance or 0.0) >= protect.AUTO_PROTECT_IMPORTANCE
|
|
2439
|
-
and not fm.get("protected")
|
|
2475
|
+
and not fm.get("protected")
|
|
2476
|
+
and importance_source != "heuristic"):
|
|
2440
2477
|
fm["protected"] = True
|
|
2441
2478
|
fm["protection_reason"] = (f"importance={float(importance):.2f}"
|
|
2442
2479
|
f"≥{protect.AUTO_PROTECT_IMPORTANCE}")
|
|
@@ -2473,6 +2510,9 @@ class MdCG:
|
|
|
2473
2510
|
"layer": layer, "tags": tags, "bucket": bucket,
|
|
2474
2511
|
"bucket_zh": bucket_zh or None,
|
|
2475
2512
|
"importance": importance, "created_at": fm["created_at"],
|
|
2513
|
+
# issue50-d:重要度来源入快照(与 _node_entry 同口径——写入后即可
|
|
2514
|
+
# 免读文件判「0.8 是显式 hint 还是启发式」,不等全量重建)。
|
|
2515
|
+
"importance_source": fm.get("importance_source"),
|
|
2476
2516
|
"verification_basis": verification_basis,
|
|
2477
2517
|
"has_neg_conditions": nodefile.has_non_applicable(sealed),
|
|
2478
2518
|
# P2-1 / P4-2①:与 `_node_entry`(重建路径)**同口径**——写路径的
|
|
@@ -2973,26 +3013,33 @@ class MdCG:
|
|
|
2973
3013
|
atomic_write(self.recent_log, "")
|
|
2974
3014
|
return len(recs)
|
|
2975
3015
|
|
|
2976
|
-
# 生效条件:无条件把 entry
|
|
3016
|
+
# 生效条件:无条件把 entry 记进 _dirty[node_id] 并经 _set_index_entry 写进 index["nodes"][node_id](桶计数与 stg 结构索引同钩,见该函数);当 len(self._dirty) >= self.autoflush 时调 flush()(autoflush 为 0 时每次标脏都立即 flush);
|
|
2977
3017
|
def _stage(self, node_id, entry):
|
|
2978
3018
|
self._dirty[node_id] = entry
|
|
2979
|
-
self.
|
|
2980
|
-
if entry.get("bucket"):
|
|
2981
|
-
self.index["buckets"][entry["bucket"]] = \
|
|
2982
|
-
self.index["buckets"].get(entry["bucket"], 0) + 1
|
|
3019
|
+
self._set_index_entry(node_id, entry)
|
|
2983
3020
|
if len(self._dirty) >= self.autoflush:
|
|
2984
3021
|
self.flush()
|
|
2985
3022
|
|
|
2986
|
-
#
|
|
2987
|
-
def
|
|
2988
|
-
"""
|
|
3023
|
+
# 生效条件:无条件把 entry 写入 index["nodes"][node_id](同键覆写 = dict 原地更新,物理位序不变);entry.get("bucket") 为真值时该桶计数 +1;self._stg_index 非 None(第 3 层结构索引已构建)时同钩增量增/改该 id,为 None(未构建/已失效)时**不构建**——首次 stg 访问才构建,写路径零额外成本;返回 None。
|
|
3024
|
+
def _set_index_entry(self, node_id, entry):
|
|
3025
|
+
"""索引条目写入的**单点**(桶计数 + stg 结构索引同钩)。
|
|
2989
3026
|
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
`
|
|
2993
|
-
|
|
2994
|
-
而够不着它,`check` 的 dangling 永不归零(本机实测累积数百条)。
|
|
3027
|
+
「同钩」是设计稿 §3.2 的字面要求:写路径的增量维护若与桶计数分家,
|
|
3028
|
+
两处口径必然漂开(本仓既有教训:同一判据写两份 = 迟早各漏一次)。
|
|
3029
|
+
无效化(rebuild/compact/重载)后 `_stg_index` 为 None ⇒ 这里退化为
|
|
3030
|
+
无操作,读侧「首次需要时构建」的惰性路径接管。
|
|
2995
3031
|
"""
|
|
3032
|
+
self.index["nodes"][node_id] = entry
|
|
3033
|
+
b = entry.get("bucket")
|
|
3034
|
+
if b:
|
|
3035
|
+
self.index["buckets"][b] = self.index["buckets"].get(b, 0) + 1
|
|
3036
|
+
ix = getattr(self, "_stg_index", None)
|
|
3037
|
+
if ix is not None:
|
|
3038
|
+
ix.add(node_id, entry)
|
|
3039
|
+
|
|
3040
|
+
# 生效条件:无条件 pop index["nodes"][node_id](不存在则无操作);被 pop 的条目有真值 bucket 时该桶计数 -1,减后 <=0 则删除该桶键;self._stg_index 非 None 时同钩摘除该 id(未构建即不动);返回被摘除的条目(不存在返回 None)。
|
|
3041
|
+
def _remove_index_entry(self, node_id):
|
|
3042
|
+
"""索引条目摘除的**单点**(与 `_set_index_entry` 对称:桶计数 + 结构索引同钩)。"""
|
|
2996
3043
|
e = self.index["nodes"].pop(node_id, None)
|
|
2997
3044
|
if e and e.get("bucket"):
|
|
2998
3045
|
b = e["bucket"]
|
|
@@ -3001,6 +3048,27 @@ class MdCG:
|
|
|
3001
3048
|
self.index["buckets"][b] = left
|
|
3002
3049
|
else:
|
|
3003
3050
|
self.index["buckets"].pop(b, None)
|
|
3051
|
+
ix = getattr(self, "_stg_index", None)
|
|
3052
|
+
if ix is not None:
|
|
3053
|
+
ix.remove(node_id)
|
|
3054
|
+
return e
|
|
3055
|
+
|
|
3056
|
+
# 生效条件:恒把 self._stg_index 置 None(丢弃已构建的第 3 层结构索引)——凡**整体替换 self.index** 的路径(重载/compact/rebuild)调它:表按身份判代际(`StgIndex.index_obj is self.index`),显式失效让下次 stg 访问重新按新快照构建(惰性失效重建,设计稿 §3.2 二选一之选);恒返回 None、不抛。
|
|
3057
|
+
def _invalidate_stg_index(self):
|
|
3058
|
+
"""第 3 层结构索引的失效单点(整体换索引的三处编排点都调它)。"""
|
|
3059
|
+
self._stg_index = None
|
|
3060
|
+
|
|
3061
|
+
# 生效条件:先经 _remove_index_entry 摘除 index["nodes"][node_id](桶计数与 stg 结构索引同钩),随后无论是否命中都把 _dirty[node_id] 置 None(删除 tombstone)并立即调 flush() 持久化;
|
|
3062
|
+
def _unstage(self, node_id):
|
|
3063
|
+
"""摘除索引条目并**持久化**——与 `_stage` 对称的删除原语。
|
|
3064
|
+
|
|
3065
|
+
只 pop 内存索引是不够的:`close()` 注释里记过同构的坑(已有
|
|
3066
|
+
`_index.json` 的根重开不重扫目录),于是「删掉的条目」会在
|
|
3067
|
+
`_index_log` 重放时复活成**幽灵条目**(索引有条目、节点文件不存在)。
|
|
3068
|
+
幽灵条目的代价:检索白跑候选、`ref action=prune` 因 `cg.get` 取不回
|
|
3069
|
+
而够不着它,`check` 的 dangling 永不归零(本机实测累积数百条)。
|
|
3070
|
+
"""
|
|
3071
|
+
self._remove_index_entry(node_id)
|
|
3004
3072
|
self._dirty[node_id] = None # None = 删除记录,随 flush 落分片日志
|
|
3005
3073
|
self.flush() # 删除不可延迟到 autoflush 阈值
|
|
3006
3074
|
|