@furongjun1999/dsh-memory 0.5.0 → 0.6.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.
Files changed (156) hide show
  1. package/README.md +572 -465
  2. package/docs/README.md +1 -0
  3. package/docs/eval/DSH/346/227/245/345/277/227/347/264/242/345/274/225v2_/345/217/202/350/200/203dsh-TUI_v1.0.md +248 -0
  4. package/docs/eval/DSH/346/227/245/345/277/227/347/264/242/345/274/225/346/225/210/346/236/234/351/252/214/350/257/201_v1.0.md +209 -0
  5. package/docs/eval/DSH/347/253/257/347/274/272/351/231/267/344/270/223/351/241/271_v1.0.md +254 -0
  6. package/docs/eval/P1b2_/350/257/273/351/235/242/344/273/243/351/231/205/344/277/256/345/244/215_v1.0.md +124 -0
  7. package/docs/eval/bench_lingshu_self/bench_self.py +140 -0
  8. package/docs/eval/bench_lingshu_self/self_bench_result.json +404 -0
  9. package/docs/eval/bench_lingshu_self//347/201/265/346/236/242/350/207/252/345/272/223/347/253/257/345/210/260/347/253/257/346/243/200/347/264/242/345/256/236/346/265/213_v1.0.md +38 -0
  10. package/docs/eval//344/270/215/345/217/257/351/235/240/346/200/247/350/220/275/345/234/260_P0_v1.0.md +185 -0
  11. package/docs/eval//345/217/221/345/270/20306_/344/270/200/351/224/256/351/205/215/347/275/256/344/270/216DSH0172_v1.0.md +171 -0
  12. package/docs/eval//346/225/205/351/232/234/346/263/250/345/205/245/345/256/236/346/265/213_v1.0.md +422 -0
  13. 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 +100 -0
  14. 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 +197 -0
  15. 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_v1.md +156 -0
  16. 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_v10.md +210 -0
  17. 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_v11.md +227 -0
  18. 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_v12.md +203 -0
  19. 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_v13.md +233 -0
  20. 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_v14.md +191 -0
  21. 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_v15.md +213 -0
  22. 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_v16.md +214 -0
  23. 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_v17.md +199 -0
  24. 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_v18.md +183 -0
  25. 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_v19.md +207 -0
  26. 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_v2.md +156 -0
  27. 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_v3.md +152 -0
  28. 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_v4.md +128 -0
  29. 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_v5.md +114 -0
  30. 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_v6.md +192 -0
  31. 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_v7.md +187 -0
  32. 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_v8.md +207 -0
  33. 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_v9.md +203 -0
  34. package/docs/hive//345/256/211/345/205/250/345/256/241/350/256/241/345/256/236/351/224/232_v0.1.md +202 -0
  35. 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 +19 -3
  36. package/docs/images/lingshu-moonlight-covenant-poster-preview.jpg +0 -0
  37. package/docs/images/lingshu-moonlight-covenant-poster.png +0 -0
  38. package/docs/mdcg//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +40 -40
  39. package/docs/theory//344/270/215/345/217/257/351/235/240/345/256/232/347/220/206/344/270/216/345/244/261/346/225/210/344/274/230/345/205/210/346/241/206/346/236/266_v0.3.md +365 -0
  40. package/docs//344/270/215/345/217/257/351/235/240/346/200/247/347/220/206/350/256/272_v0.1.md +343 -0
  41. package/dsh/cordis-patch-profile-web.example.yml +35 -0
  42. package/lib/bridge.d.ts +9 -0
  43. package/lib/bridge.js +35 -0
  44. package/lib/cli.d.ts +3 -0
  45. package/lib/cli.js +66 -0
  46. package/lib/index.js +7 -1
  47. package/lib/init.d.ts +88 -0
  48. package/lib/init.js +287 -0
  49. package/lib/lib/roleplay_web.js +530 -443
  50. package/lib/lib/token_store.d.ts +7 -1
  51. package/lib/lib/token_store.js +12 -3
  52. package/md_cg/audit.py +12 -1
  53. package/md_cg/backfill.py +16 -15
  54. package/md_cg/backfill_bucket_zh.py +35 -0
  55. package/md_cg/bench_e2e_judge.py +532 -0
  56. package/md_cg/bench_e2e_locomo_qa.py +368 -0
  57. package/md_cg/bench_e2e_qa.py +256 -0
  58. package/md_cg/branches.py +18 -2
  59. package/md_cg/ccgc.py +3 -2
  60. package/md_cg/chain.py +19 -4
  61. package/md_cg/consolidate.py +7 -6
  62. package/md_cg/crosscheck.py +4 -3
  63. package/md_cg/crypto.py +439 -437
  64. package/md_cg/datapath.py +395 -335
  65. package/md_cg/evidence.py +8 -3
  66. package/md_cg/export.py +3 -1
  67. package/md_cg/forgetting.py +2 -2
  68. package/md_cg/fsutil.py +48 -0
  69. package/md_cg/hotcache.py +255 -238
  70. package/md_cg/interop.py +161 -22
  71. package/md_cg/judgment_manifest.py +177 -0
  72. package/md_cg/links.py +655 -622
  73. package/md_cg/mcp_server.py +282 -31
  74. package/md_cg/mdcg.py +702 -126
  75. package/md_cg/mdcos.py +454 -68
  76. package/md_cg/mreview/govern.py +5 -4
  77. package/md_cg/postings.py +4 -2
  78. package/md_cg/readcache.py +76 -18
  79. package/md_cg/reconcile.py +228 -0
  80. package/md_cg/review_cli.py +215 -0
  81. package/md_cg/routing.py +28 -0
  82. package/md_cg/run_tests.py +211 -0
  83. package/md_cg/scrub.py +862 -852
  84. package/md_cg/security.py +385 -275
  85. package/md_cg/selfreport.py +3 -2
  86. package/md_cg/signer.py +565 -562
  87. package/md_cg/sources.py +3 -2
  88. package/md_cg/stg.py +6 -0
  89. package/md_cg/sustain.py +1168 -1138
  90. package/md_cg/test_access_hints.py +147 -0
  91. package/md_cg/test_branch_discard_tombstone.py +136 -0
  92. package/md_cg/test_branches.py +259 -249
  93. package/md_cg/test_chain_read_isolate.py +168 -0
  94. package/md_cg/test_datapath_device_name.py +203 -0
  95. package/md_cg/test_emit_negtail_cache.py +156 -0
  96. package/md_cg/test_en_pipeline.py +186 -166
  97. package/md_cg/test_govern_directread.py +421 -0
  98. package/md_cg/test_i32_hotcache_env_key.py +218 -0
  99. package/md_cg/test_identity_attribution.py +228 -147
  100. package/md_cg/test_index_crossprocess_reload.py +301 -0
  101. package/md_cg/test_index_durability.py +238 -224
  102. package/md_cg/test_interop.py +4 -2
  103. package/md_cg/test_interop_judgment.py +228 -0
  104. package/md_cg/test_issue39_utf8_stdio.py +273 -0
  105. package/md_cg/test_links_concurrent_write.py +188 -0
  106. package/md_cg/test_merge_upsert.py +168 -0
  107. package/md_cg/test_n123_derive_expiry_chain.py +205 -0
  108. package/md_cg/test_n130_verify_falsified_protect.py +185 -0
  109. package/md_cg/test_n131_merge_gate.py +205 -0
  110. package/md_cg/test_none_id_write_guard.py +165 -0
  111. package/md_cg/test_p1x_ref_root.py +160 -0
  112. package/md_cg/test_p27_docindex.py +774 -765
  113. package/md_cg/test_p2_mcp.py +3 -0
  114. package/md_cg/test_p32_backfill.py +304 -298
  115. package/md_cg/test_p39_verify_flow.py +90 -50
  116. package/md_cg/test_p47_session_view.py +60 -25
  117. package/md_cg/test_propose_tail_index.py +157 -0
  118. package/md_cg/test_read_scope_b27.py +277 -0
  119. package/md_cg/test_readcache_default_on.py +168 -0
  120. package/md_cg/test_readcache_precise_inval.py +270 -0
  121. package/md_cg/test_readcache_prodpath.py +55 -7
  122. package/md_cg/test_reconcile_v0.py +294 -0
  123. package/md_cg/test_retr_s1.py +6 -2
  124. package/md_cg/test_retr_s1b.py +67 -0
  125. package/md_cg/test_retr_s7.py +8 -0
  126. package/md_cg/test_retr_s9_entity_ctx.py +181 -175
  127. package/md_cg/test_retr_score_once.py +208 -0
  128. package/md_cg/test_review_cli_attribution.py +177 -0
  129. package/md_cg/test_review_cli_visibility.py +235 -0
  130. package/md_cg/test_review_onepass.py +170 -0
  131. package/md_cg/test_rrf_graph_seed_cache.py +154 -0
  132. package/md_cg/test_security_audit.py +155 -0
  133. package/md_cg/test_security_audit_b26.py +161 -0
  134. package/md_cg/test_security_audit_v21.py +250 -0
  135. package/md_cg/test_semantic_canonical.py +255 -241
  136. package/md_cg/test_session_isolation.py +168 -0
  137. package/md_cg/test_snapshot_autoclose.py +187 -0
  138. package/md_cg/test_tail_watermark_race.py +208 -0
  139. package/md_cg/test_tenant_env_override_warn.py +139 -0
  140. package/md_cg/test_tenant_registry_corrupt_warn.py +151 -0
  141. package/md_cg/test_v14_fixes.py +415 -397
  142. package/md_cg/test_verify_dirty_reconcile.py +157 -0
  143. package/md_cg/theory.py +276 -273
  144. package/md_cg/tokens.py +734 -677
  145. package/md_cg/units.py +3 -2
  146. package/md_cg/vision_evidence.py +4 -3
  147. package/md_cg/whitebox_kb/wisdom/code_solidified.json +6195 -6195
  148. package/md_cg/whitebox_kb/wisdom/multilang_ir.py +128 -124
  149. package/md_cg/writepipe.py +554 -550
  150. package/package.json +11 -2
  151. package/src/bridge.ts +434 -401
  152. package/src/cli.ts +65 -0
  153. package/src/index.ts +526 -518
  154. package/src/init.ts +347 -0
  155. package/src/lib/roleplay_web.ts +1019 -932
  156. package/src/lib/token_store.ts +202 -192
@@ -25,7 +25,13 @@ export interface ResolveTokenOptions {
25
25
  /** 密钥环路径覆盖(默认 ~/.mdcg/token)。 */
26
26
  keyringPath?: string;
27
27
  }
28
- /** 默认密钥环路径:与 md_cg 的 `~/.mdcg/` 约定同目录(`_tokens.json` 亦在此)。 */
28
+ /** 辅助存储根(密钥/令牌/信任/理论/心跳):与 md_cg 的 `datapath.aux_root()` 同口径。
29
+ *
30
+ * 默认 `~/.mdcg`(历史值,**逐字保留**——改默认会让存量密钥/令牌失联);
31
+ * `MDCG_AUX_ROOT` 可把它们整体搬走(沙箱/多用户/与记忆真源同处的场景)。
32
+ * 与 `config.mdcg.root`(记忆真源)有意分离:身份与信任不随认知图迁移。 */
33
+ export declare function auxRoot(): string;
34
+ /** 默认密钥环路径:与 md_cg 的 `_tokens.json` 同目录(aux 根下,见 auxRoot())。 */
29
35
  export declare function defaultKeyringPath(): string;
30
36
  /**
31
37
  * 解析写入凭据:显式配置 → 进程 env → 密钥环 → 首启自动签发。
@@ -29,11 +29,20 @@
29
29
  import { spawnSync } from 'node:child_process';
30
30
  import { chmodSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
31
31
  import { homedir } from 'node:os';
32
- import { dirname, join } from 'node:path';
32
+ import { dirname, join, resolve } from 'node:path';
33
33
  import { pythonPathValue, runRoot } from './datapath.js';
34
- /** 默认密钥环路径:与 md_cg 的 `~/.mdcg/` 约定同目录(`_tokens.json` 亦在此)。 */
34
+ /** 辅助存储根(密钥/令牌/信任/理论/心跳):与 md_cg 的 `datapath.aux_root()` 同口径。
35
+ *
36
+ * 默认 `~/.mdcg`(历史值,**逐字保留**——改默认会让存量密钥/令牌失联);
37
+ * `MDCG_AUX_ROOT` 可把它们整体搬走(沙箱/多用户/与记忆真源同处的场景)。
38
+ * 与 `config.mdcg.root`(记忆真源)有意分离:身份与信任不随认知图迁移。 */
39
+ export function auxRoot() {
40
+ const env = (process.env.MDCG_AUX_ROOT ?? '').trim();
41
+ return env ? resolve(env) : join(homedir(), '.mdcg');
42
+ }
43
+ /** 默认密钥环路径:与 md_cg 的 `_tokens.json` 同目录(aux 根下,见 auxRoot())。 */
35
44
  export function defaultKeyringPath() {
36
- return join(homedir(), '.mdcg', 'token');
45
+ return join(auxRoot(), 'token');
37
46
  }
38
47
  /** 读取密钥环;不存在 / 不可读 / 空文件一律返回 undefined(不抛异常)。 */
39
48
  function readKeyring(path) {
package/md_cg/audit.py CHANGED
@@ -168,7 +168,9 @@ def _verify_code(payload, ctx):
168
168
  return _verdict(REJECT, "code", f"语法错误 L{exc.lineno}: {exc.msg}")
169
169
  n_def = sum(isinstance(x, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))
170
170
  for x in ast.walk(tree))
171
- cmd = payload.get("test_cmd") or os.environ.get("MDCG_CODE_TEST_CMD")
171
+ # P2-18(批次 30,外部审查报告):test_cmd 只允许服务端 env 配置——
172
+ # payload 是模型可控输入面,一旦接到工具参数顶层即为 RCE 注入点。
173
+ cmd = os.environ.get("MDCG_CODE_TEST_CMD")
172
174
  if not cmd:
173
175
  return _verdict(ACCEPT, "code",
174
176
  f"AST 解析通过({n_def} 个定义);未配置 test_cmd,仅静态验证")
@@ -323,7 +325,15 @@ def load_external_verifiers(modules=None, strict=False):
323
325
  if spec is None:
324
326
  spec = os.environ.get(VERIFIER_MODULES_ENV) or ""
325
327
  names = [x.strip() for x in str(spec).split(",") if x.strip()]
328
+ # P2-19(批次 30):模块名格式白名单——拒绝空串外的异常形态(路径分隔/
329
+ # 通配/扩展名等非 import 路径输入),加载动作本身写 stderr(可见性)。
330
+ import re as _re
331
+ _bad = [n for n in names if not _re.match(r"^[A-Za-z_][A-Za-z0-9_.]*$", n)]
326
332
  rep = {"loaded": [], "failed": [], "verifiers": {}}
333
+ for n in _bad:
334
+ names.remove(n)
335
+ rep["failed"].append({"module": n,
336
+ "error": "模块名不符合 import 路径格式(P2-19 白名单)"})
327
337
  if not names:
328
338
  return rep
329
339
  this = _sys.modules[__name__]
@@ -336,6 +346,7 @@ def load_external_verifiers(modules=None, strict=False):
336
346
  if strict:
337
347
  raise
338
348
  continue
349
+ _sys.stderr.write("[mdcg-audit] 已加载外部验证器模块: %s\n" % name)
339
350
  fn = getattr(mod, "register", None)
340
351
  if callable(fn):
341
352
  try:
package/md_cg/backfill.py CHANGED
@@ -51,6 +51,7 @@ from . import crypto, nodefile, tokens
51
51
  from .consolidate import _has_ccg_line, _upsert_ccg_line
52
52
  from .fsutil import append_jsonl, read_jsonl
53
53
  from .mdcos import MdCGOS, _ccg_field
54
+ from .readcache import direct_read
54
55
 
55
56
  # ---- 常量 ----------------------------------------------------------------
56
57
 
@@ -293,7 +294,7 @@ def _readable_guard(cg, e) -> bool:
293
294
  # 生效条件:仅当 cg 对 e 读出的 fm 非 None、content 未被 crypto.is_encrypted、e['layer'] 不在 SKIP_LAYERS 且 fm.get('tags') 无命中 SKIP_TAGS、ccg_completeness(content)['complete'] 为假时才继续——derive_fields(受 basis_text 影响)过滤掉 content 已有 CCG 行的可写字段为空时按 placeholder_out 是否非空返回 ('placeholder'/'unfillable', None),非空时返回 ('', item)(item 的 id 取 nid、class 依 undeducible 是否为空取 'backfillable' 或 'partial');上述四个前置不满足时依次返回 ('unreadable'/'locked'/'derived'/'present', None)。
294
295
  def _classify(cg, e, nid, basis_text=None):
295
296
  """→ (skip_reason, item);skip_reason 非空表示不参与回填。"""
296
- fm, content = cg._read(e)
297
+ fm, content = direct_read(cg, e)
297
298
  if fm is None:
298
299
  return "unreadable", None
299
300
  if crypto.is_encrypted(content):
@@ -421,7 +422,7 @@ def apply(x, ids=None, entry_ids=None, layer=None, limit=None,
421
422
  if not e:
422
423
  rep["skipped_drift"] += 1
423
424
  continue
424
- fm, content = cg._read(e)
425
+ fm, content = direct_read(cg, e)
425
426
  if fm is None or crypto.is_encrypted(content):
426
427
  rep["skipped_locked"] += 1
427
428
  continue
@@ -474,7 +475,7 @@ def apply(x, ids=None, entry_ids=None, layer=None, limit=None,
474
475
  return rep
475
476
 
476
477
 
477
- # 生效条件:x 经 _as_cg 定位后读留痕日志,仅对 action=='backfill' 且(batch 为假值则不过滤 batch,否则 rec['batch']==batch)、(entry_ids 为假值则不过滤,否则 rec['entry_id'] 属于该集合)、write_id 未出现在已完成 rollback 集合中、节点命中 cg.index['nodes'] 且 cg._read(e) 的 fm 可读、字段当前 _ccg_field(content, f) 等于留痕 after 的记录执行撤销写回(before 为 None 则删该 comment 键,否则还原原值),无字段可撤销只计 conflict 不写盘,reverted 非空时 rebuild_index,结果汇总进返回的 rep。
478
+ # 生效条件:x 经 _as_cg 定位后读留痕日志,仅对 action=='backfill' 且(batch 为假值则不过滤 batch,否则 rec['batch']==batch)、(entry_ids 为假值则不过滤,否则 rec['entry_id'] 属于该集合)、write_id 未出现在已完成 rollback 集合中、节点命中 cg.index['nodes'] 且 direct_read(cg, e) 的 fm 可读、字段当前 _ccg_field(content, f) 等于留痕 after 的记录执行撤销写回(before 为 None 则删该 comment 键,否则还原原值),无字段可撤销只计 conflict 不写盘,reverted 非空时 rebuild_index,结果汇总进返回的 rep。
478
479
  def rollback(x, batch=None, entry_ids=None, actor=None) -> dict:
479
480
  """按留痕反向应用:撤销本批次回填(当前值 ≠ 写入值时跳过,防覆盖)。"""
480
481
  cg = _as_cg(x)
@@ -500,7 +501,7 @@ def rollback(x, batch=None, entry_ids=None, actor=None) -> dict:
500
501
  if not e:
501
502
  rep["missing"] += 1
502
503
  continue
503
- fm, content = cg._read(e)
504
+ fm, content = direct_read(cg, e)
504
505
  if fm is None:
505
506
  rep["missing"] += 1
506
507
  continue
@@ -627,7 +628,7 @@ def cap_plan(x, layer=None, limit=None, ids=None, min_conf=0.5) -> dict:
627
628
  if not _readable_guard(cg, e):
628
629
  rep["skipped_denied"] += 1
629
630
  continue
630
- fm, content = cg._read(e)
631
+ fm, content = direct_read(cg, e)
631
632
  if fm is None or crypto.is_encrypted(content):
632
633
  rep["skipped_locked"] += 1
633
634
  continue
@@ -671,7 +672,7 @@ def cap_apply(x, ids=None, entry_ids=None, layer=None, limit=None,
671
672
  if not e:
672
673
  rep["skipped_drift"] += 1
673
674
  continue
674
- fm, content = cg._read(e)
675
+ fm, content = direct_read(cg, e)
675
676
  if fm is None or crypto.is_encrypted(content):
676
677
  rep["skipped_locked"] += 1
677
678
  continue
@@ -725,7 +726,7 @@ def cap_rollback(x, batch=None, entry_ids=None, actor=None) -> dict:
725
726
  if not e:
726
727
  rep["missing"] += 1
727
728
  continue
728
- fm, content = cg._read(e)
729
+ fm, content = direct_read(cg, e)
729
730
  if fm is None:
730
731
  rep["missing"] += 1
731
732
  continue
@@ -798,7 +799,7 @@ def exempt_plan(x, layer=None, limit=None, ids=None, require_ready=True,
798
799
  if not _readable_guard(cg, e):
799
800
  rep["skipped_denied"] += 1
800
801
  continue
801
- fm, content = cg._read(e)
802
+ fm, content = direct_read(cg, e)
802
803
  if fm is None or crypto.is_encrypted(content):
803
804
  rep["skipped_locked"] += 1
804
805
  continue
@@ -859,7 +860,7 @@ def exempt_apply(x, ids=None, entry_ids=None, layer=None, limit=None,
859
860
  if not e:
860
861
  rep["skipped_drift"] += 1
861
862
  continue
862
- fm, content = cg._read(e)
863
+ fm, content = direct_read(cg, e)
863
864
  if fm is None or crypto.is_encrypted(content):
864
865
  rep["skipped_locked"] += 1
865
866
  continue
@@ -917,7 +918,7 @@ def exempt_rollback(x, batch=None, entry_ids=None, actor=None) -> dict:
917
918
  if not e:
918
919
  rep["missing"] += 1
919
920
  continue
920
- fm, content = cg._read(e)
921
+ fm, content = direct_read(cg, e)
921
922
  if fm is None:
922
923
  rep["missing"] += 1
923
924
  continue
@@ -973,7 +974,7 @@ def conditions_pending(x, prefix=None, layer=None, limit=None) -> dict:
973
974
  if not _readable_guard(cg, e):
974
975
  rep["skipped_denied"] += 1
975
976
  continue
976
- fm, content = cg._read(e)
977
+ fm, content = direct_read(cg, e)
977
978
  if fm is None or crypto.is_encrypted(content):
978
979
  rep["skipped_locked"] += 1
979
980
  continue
@@ -1059,7 +1060,7 @@ def fix_conditions_plan(x, prefix=None, limit=None) -> dict:
1059
1060
  if not _readable_guard(cg, e):
1060
1061
  rep["skipped_denied"] += 1
1061
1062
  continue
1062
- fm, content = cg._read(e)
1063
+ fm, content = direct_read(cg, e)
1063
1064
  if fm is None or crypto.is_encrypted(content):
1064
1065
  rep["skipped_locked"] += 1
1065
1066
  continue
@@ -1116,7 +1117,7 @@ def fix_conditions_apply(x, ids=None, entry_ids=None, prefix=None, limit=None,
1116
1117
  if not e:
1117
1118
  rep["skipped_drift"] += 1
1118
1119
  continue
1119
- fm, content = cg._read(e)
1120
+ fm, content = direct_read(cg, e)
1120
1121
  if fm is None or crypto.is_encrypted(content):
1121
1122
  rep["skipped_locked"] += 1
1122
1123
  continue
@@ -1206,7 +1207,7 @@ def fix_conditions_rollback(x, batch=None, entry_ids=None, actor=None) -> dict:
1206
1207
  if not e:
1207
1208
  rep["missing"] += 1
1208
1209
  continue
1209
- fm, content = cg._read(e)
1210
+ fm, content = direct_read(cg, e)
1210
1211
  if fm is None:
1211
1212
  rep["missing"] += 1
1212
1213
  continue
@@ -1260,7 +1261,7 @@ def verify_conditions(x, prefix=None, limit=None) -> dict:
1260
1261
  continue
1261
1262
  if e.get("layer") in INTERNAL_LAYERS:
1262
1263
  continue
1263
- fm, content = cg._read(e)
1264
+ fm, content = direct_read(cg, e)
1264
1265
  if fm is None or crypto.is_encrypted(content):
1265
1266
  continue
1266
1267
  rep["nodes_scanned"] += 1
@@ -0,0 +1,35 @@
1
+ # -*- coding: utf-8 -*-
2
+ """bucket_zh 中文别名回填(issue #33:S1b 跨语言收敛;幂等、支持 --dry-run)。
3
+
4
+ 用法:
5
+ python -X utf8 -m md_cg.backfill_bucket_zh --root <库根> [--dry-run] [--limit N]
6
+
7
+ 为何需要:别名是**写入侧**新增元数据(英文桶键节点落 `fm.bucket_zh`),存量
8
+ 节点没有——不回填则 S1b 对存量英文桶键仍走 cross_lang_no_match 审计兜底。
9
+ 回填后中文 query 可收敛到英文桶(别名参与 domain_similarity,零翻译依赖)。
10
+ """
11
+ import argparse
12
+ import json
13
+ import sys
14
+
15
+ from .mdcg import MdCG
16
+
17
+
18
+ # 生效条件:无条件解析参数(root 必填,dry_run/limit 可选)、以 MdCG(root) 打开库、调用 backfill_bucket_zh 并把统计 dict 以 JSON 打印,返回 0。
19
+ def main(argv=None):
20
+ ap = argparse.ArgumentParser()
21
+ ap.add_argument("--root", required=True, help="库根目录")
22
+ ap.add_argument("--dry-run", action="store_true", dest="dry_run",
23
+ help="只统计不改盘")
24
+ ap.add_argument("--limit", type=int, default=None, help="最多补写多少个节点")
25
+ a = ap.parse_args(argv)
26
+ cg = MdCG(a.root)
27
+ st = cg.backfill_bucket_zh(dry_run=a.dry_run, limit=a.limit)
28
+ print(json.dumps(st, ensure_ascii=False))
29
+ return 0
30
+
31
+
32
+ if __name__ == "__main__":
33
+ if hasattr(sys.stdout, "reconfigure"):
34
+ sys.stdout.reconfigure(encoding="utf-8")
35
+ sys.exit(main())