@furongjun1999/dsh-memory 0.6.0 → 0.7.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 (202) hide show
  1. package/README.md +618 -572
  2. package/codebuddy/CODEBUDDY.md +10 -10
  3. package/data/policy.json +27 -0
  4. package/docs/README.md +1 -0
  5. package/docs/discipline/harnesses.yaml +31 -0
  6. package/docs/discipline/templates/full.md.tmpl +1 -1
  7. package/docs/discipline/templates/rules.mdc.tmpl +1 -1
  8. package/docs/eval/N225_/347/264/242/345/274/225/346/227/245/345/277/227/351/235/236/345/257/271/350/261/241/350/243/205/350/275/275/351/235/242/347/261/273/345/236/213/351/227/270_v1.0.md +418 -0
  9. package/docs/eval/issue43_/351/273/230/350/256/244/347/255/226/347/225/245/344/270/216/351/224/256/347/261/273/345/236/213/351/227/270_v1.1.md +441 -0
  10. package/docs/eval/issue43_/351/273/230/350/256/244/347/255/226/347/225/245/345/212/240/350/275/275/344/270/216/345/207/255/346/215/256/346/230/216/346/226/207/351/230/237/345/210/227_v1.0.md +398 -0
  11. package/docs/eval//344/274/230/345/214/226/347/254/254/344/270/200/346/211/271_/346/216/245/347/272/277/344/270/216/347/255/211/344/273/267/345/217/230/346/215/242_v1.0.md +498 -0
  12. package/docs/eval//344/274/230/345/214/226/347/254/254/344/270/211/346/211/271_/351/227/250/347/246/201/350/275/254/346/255/243/344/270/216/350/260/203/345/272/246/346/255/242/350/241/200/344/270/216/351/227/250/346/216/247/346/224/266/345/217/243_v1.0.md +669 -0
  13. package/docs/eval//344/274/230/345/214/226/347/254/254/344/272/214/346/211/271_/344/276/235/350/265/226/351/200/217/344/274/240/344/270/216/345/257/271/346/213/215/345/217/243/345/276/204/344/270/216/350/264/237/347/274/223/345/255/230_v1.0.md +683 -0
  14. package/docs/eval//345/207/272/350/264/247/351/235/242/345/206/222/347/203/237_/350/277/233/350/264/247/351/227/250/347/246/201_v1.0.md +460 -0
  15. package/docs/eval//345/217/221/345/270/20307_/345/244/226/351/203/250/346/212/245/345/221/212/345/233/233/346/211/271/344/277/256/345/244/215/344/270/216/346/217/222/344/273/266/351/235/242/345/212/240/345/233/272_v1.0.md +205 -0
  16. package/docs/eval//345/217/221/345/270/20308_/350/207/252/350/277/255/344/273/243/344/270/216/347/235/241/347/234/240_/345/233/276/346/243/200/347/264/242/350/267/257/344/270/216/346/235/203/351/207/215_v1.0.md +97 -0
  17. package/docs/eval//345/275/222/344/270/200/345/261/202/347/274/272/347/234/201/347/277/273/345/205/263_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +458 -0
  18. package/docs/eval//346/200/247/350/203/275/344/270/223/351/241/271_/345/206/267/346/237/245/350/257/242/344/270/216/345/206/205/345/255/230_v1.0.md +218 -0
  19. package/docs/eval//346/225/205/351/232/234/346/263/250/345/205/245/345/256/236/346/265/213_v1.0.md +10 -0
  20. package/docs/eval//347/274/226/347/240/201/351/235/242/345/211/215/347/275/256_/345/205/245/345/217/243/350/207/252/344/277/235/350/257/201UTF8/344/270/216/346/226/207/346/234/254open/345/256/210/345/215/253_v1.0.md +646 -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_v20.md +283 -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_v21.md +224 -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_v22.md +223 -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_v23.md +293 -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_v24.md +303 -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_v25.md +230 -0
  27. package/docs/hive//346/243/200/347/264/242/347/256/227/346/263/225/345/217/243/345/276/204/345/257/271/347/205/247_v0.1.md +136 -11
  28. package/docs/hive//346/243/200/347/264/242/350/267/257/345/276/204/344/270/216/350/256/244/347/237/245/347/273/223/346/236/204/345/245/221/347/272/246_v0.1.md +52 -2
  29. package/docs/hive//350/234/202/345/267/242M6_ingest/345/256/236/346/226/275/350/256/241/345/210/222_v0.1.md +1 -1
  30. package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +100 -0
  31. 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
  32. package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +57 -9
  33. package/docs/mdcg//347/201/265/346/236/242/350/256/260/345/277/206/345/212/250/350/257/215/345/215/217/350/256/256_v1.0-draft.md +7 -2
  34. 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 +183 -0
  35. package/docs/mdcg//350/256/244/347/237/245/345/233/276_/347/264/242/345/274/225/344/270/216/345/267/245/347/250/213/350/247/204/350/214/203/345/214/226_/350/256/241/345/210/222_v0.1.md +39 -3
  36. package/docs/plans//345/205/250/344/270/255/346/226/207/347/274/226/347/240/201/344/270/216/350/234/202/345/267/242/344/273/273/345/212/241/346/240/207/350/257/206/345/245/221/347/272/246_v2.0.md +220 -0
  37. package/docs/plans//347/234/237/346/272/220/347/264/242/345/274/225_/351/200/232/347/224/250/346/234/272/345/210/266_v0.3.md +227 -0
  38. package/docs/plans//347/235/241/347/234/240/344/270/216/350/207/252/350/277/255/344/273/243_/345/212/237/350/203/275/344/274/230/345/214/226/350/256/276/350/256/241_v0.4.md +547 -0
  39. package/docs/plans//350/234/202/345/267/242/346/250/241/345/236/213/345/257/206/351/222/245/351/205/215/347/275/256/351/235/242_v1.0.md +385 -0
  40. 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 +460 -460
  41. package/lib/bridge.js +24 -2
  42. package/lib/hooks.d.ts +31 -2
  43. package/lib/hooks.js +219 -16
  44. package/lib/init.js +16 -1
  45. package/lib/lib/prompt_safety.d.ts +52 -1
  46. package/lib/lib/prompt_safety.js +76 -1
  47. package/md_cg/audit.py +652 -379
  48. package/md_cg/bench6_arms.py +1 -1
  49. package/md_cg/bench_e2e_locomo_qa.py +11 -4
  50. package/md_cg/bench_p0.py +1 -1
  51. package/md_cg/branches.py +2 -2
  52. package/md_cg/ccgc.py +1071 -1006
  53. package/md_cg/chain.py +48 -1
  54. package/md_cg/consistency.py +97 -9
  55. package/md_cg/consolidate.py +34 -9
  56. package/md_cg/crypto.py +59 -26
  57. package/md_cg/docindex.py +16 -1
  58. package/md_cg/evolution.py +17 -1
  59. package/md_cg/export.py +1 -1
  60. package/md_cg/forgetting.py +309 -13
  61. package/md_cg/freshness.py +511 -0
  62. package/md_cg/fsutil.py +454 -8
  63. package/md_cg/generation.py +409 -0
  64. package/md_cg/hotcache.py +4 -1
  65. package/md_cg/identity.py +3 -3
  66. package/md_cg/insight.py +24 -3
  67. package/md_cg/linkref.py +1 -1
  68. package/md_cg/logref.py +327 -0
  69. package/md_cg/mcp_server.py +4261 -3818
  70. package/md_cg/mdcg.py +4695 -3547
  71. package/md_cg/mdcos.py +4672 -4159
  72. package/md_cg/mreview/pipeline.py +15 -1
  73. package/md_cg/nodefile.py +713 -576
  74. package/md_cg/protect.py +158 -11
  75. package/md_cg/protocol.py +41 -2
  76. package/md_cg/provenance.py +22 -4
  77. package/md_cg/reach.py +4 -4
  78. package/md_cg/readcache.py +76 -15
  79. package/md_cg/reconcile.py +1 -1
  80. package/md_cg/refindex.py +246 -37
  81. package/md_cg/refine.py +1 -1
  82. package/md_cg/routing.py +32 -5
  83. package/md_cg/run_tests.py +17 -0
  84. package/md_cg/scrub.py +30 -3
  85. package/md_cg/security.py +47 -1
  86. package/md_cg/self_state.py +5 -5
  87. package/md_cg/semantic/canonical.py +22 -0
  88. package/md_cg/semantic/unify.py +73 -22
  89. package/md_cg/semantic/unify_fixture.json +25 -0
  90. package/md_cg/sleep.py +1297 -0
  91. package/md_cg/sources.py +23 -6
  92. package/md_cg/srcindex.py +352 -0
  93. package/md_cg/stg.py +47 -3
  94. package/md_cg/subgraph.py +2 -2
  95. package/md_cg/sustain.py +1436 -1168
  96. package/md_cg/tasks.py +469 -470
  97. package/md_cg/test_auto_defaults.py +424 -0
  98. package/md_cg/test_b1_auto_id_multiproc.py +277 -0
  99. package/md_cg/test_b1b2_write_face.py +417 -0
  100. package/md_cg/test_b2_sensitivity_landing.py +223 -0
  101. package/md_cg/test_b3_merge_keeps_content.py +576 -0
  102. package/md_cg/test_b4_shard_dir_selfheal.py +843 -0
  103. package/md_cg/test_b4_shard_dir_selfheal_guard.py +238 -0
  104. package/md_cg/test_boundary_hit.py +410 -0
  105. package/md_cg/test_c3_transient_read_negative.py +793 -0
  106. package/md_cg/test_c8_search_rrf_gates.py +502 -0
  107. package/md_cg/test_ccg_form_parity.py +188 -0
  108. package/md_cg/test_ccgc.py +28 -3
  109. package/md_cg/test_en_pipeline.py +22 -13
  110. package/md_cg/test_generation_guard.py +352 -0
  111. package/md_cg/test_govern_directread.py +17 -4
  112. package/md_cg/test_h2_session_view_norm.py +233 -0
  113. package/md_cg/test_h4_sustain_snapshot.py +1387 -0
  114. package/md_cg/test_hive_ingest.py +285 -285
  115. package/md_cg/test_issue39_utf8_stdio.py +307 -16
  116. package/md_cg/test_issue43_default_policy.py +865 -0
  117. package/md_cg/test_legacy_p3_node_id_type.py +375 -0
  118. package/md_cg/test_linkref.py +10 -3
  119. package/md_cg/test_lock.py +2 -2
  120. package/md_cg/test_logref.py +1109 -0
  121. package/md_cg/test_m3_h9_bucket_health_protect_mark.py +301 -0
  122. package/md_cg/test_m3_h9_semantic_guard.py +525 -0
  123. package/md_cg/test_mr_m2.py +15 -3
  124. package/md_cg/test_n130_verify_falsified_protect.py +1 -1
  125. package/md_cg/test_n139_dek_provision_failclosed.py +185 -0
  126. package/md_cg/test_n176_link_trust.py +221 -0
  127. package/md_cg/test_n178_units_jobid_gate.py +212 -0
  128. package/md_cg/test_n184_keys_concurrent_provision.py +307 -0
  129. package/md_cg/test_n195_writepath_reload.py +283 -0
  130. package/md_cg/test_n196_stale_gate_skip.py +169 -0
  131. package/md_cg/test_n197_n208_write_face_gates.py +471 -0
  132. package/md_cg/test_n198_tokens_corrupt_failclosed.py +225 -0
  133. package/md_cg/test_n199_tokens_concurrent_write.py +378 -0
  134. package/md_cg/test_n201_proposal_visibility.py +382 -0
  135. package/md_cg/test_n202_session_notes_visibility.py +461 -0
  136. package/md_cg/test_n204_n205_n226_n227_n228_n229_exit_gates.py +542 -0
  137. package/md_cg/test_n206_stdio_jsonrpc_type.py +403 -0
  138. package/md_cg/test_n209_verify_write_face_gates.py +461 -0
  139. package/md_cg/test_n212_n213_n224_generation_gates.py +519 -0
  140. package/md_cg/test_n214_n215_n221_n222_write_face_gates.py +563 -0
  141. package/md_cg/test_n225_nonobject_load.py +1110 -0
  142. package/md_cg/test_n230_dirty_replay.py +375 -0
  143. package/md_cg/test_n62_tenant_bind_failclosed.py +200 -0
  144. package/md_cg/test_neg_condition_hits.py +333 -0
  145. package/md_cg/test_neg_tail_honesty.py +663 -0
  146. package/md_cg/test_none_id_write_guard.py +4 -3
  147. package/md_cg/test_opt_batch1_md_cg.py +451 -0
  148. package/md_cg/test_p1.py +5 -5
  149. package/md_cg/test_p11_consistency.py +161 -12
  150. package/md_cg/test_p26_refindex.py +2 -2
  151. package/md_cg/test_p27_docindex.py +777 -774
  152. package/md_cg/test_p28_refcheck.py +740 -13
  153. package/md_cg/test_p29_session_ingest_export.py +2 -2
  154. package/md_cg/test_p2_mcp.py +12 -1
  155. package/md_cg/test_p2_six_elements.py +463 -0
  156. package/md_cg/test_p30_maintain.py +33 -2
  157. package/md_cg/test_p31_insight.py +1 -0
  158. package/md_cg/test_p3_legacy_closure.py +433 -0
  159. package/md_cg/test_p4_freshness.py +680 -0
  160. package/md_cg/test_p8_subgraph_chain.py +14 -1
  161. package/md_cg/test_p9c_dedup_hints.py +276 -0
  162. package/md_cg/test_policy_required_ccg.py +426 -0
  163. package/md_cg/test_protocol.py +22 -0
  164. package/md_cg/test_rank_parity_score_mode.py +522 -0
  165. package/md_cg/test_read_face_input_gates.py +363 -0
  166. package/md_cg/test_read_face_semantics.py +489 -0
  167. package/md_cg/test_recall_face_guards.py +812 -0
  168. package/md_cg/test_rejected_credential_forms.py +188 -0
  169. package/md_cg/test_rejected_redact.py +146 -0
  170. package/md_cg/test_retr_s1b.py +2 -2
  171. package/md_cg/test_retr_s5.py +20 -7
  172. package/md_cg/test_review_conformance.py +21 -3
  173. package/md_cg/test_security_audit_v21.py +2 -2
  174. package/md_cg/test_semantic_canonical.py +5 -3
  175. package/md_cg/test_server_version.py +67 -0
  176. package/md_cg/test_sleep.py +611 -0
  177. package/md_cg/test_sleep_p1.py +784 -0
  178. package/md_cg/test_srcindex.py +171 -0
  179. package/md_cg/test_tenant_env_override_warn.py +63 -50
  180. package/md_cg/test_time_core_lint.py +968 -0
  181. package/md_cg/test_token_lowercase_form.py +315 -0
  182. package/md_cg/test_unify_default_off.py +701 -0
  183. package/md_cg/test_unify_scope.py +182 -0
  184. package/md_cg/test_v21r1_package_version.py +310 -0
  185. package/md_cg/test_writepipe.py +10 -4
  186. package/md_cg/tokens.py +225 -95
  187. package/md_cg/tool_face.py +4 -4
  188. package/md_cg/trust.py +49 -5
  189. package/md_cg/units.py +204 -6
  190. package/md_cg/weights.py +4 -4
  191. package/md_cg/whitebox_kb/aeis_core/time_core.py +8 -0
  192. package/md_cg/whitebox_kb/wisdom/knowledge_points.py +1 -1
  193. package/md_cg/writelimit.py +42 -8
  194. package/md_cg/writepipe.py +651 -554
  195. package/package.json +4 -2
  196. package/skills/plugin.json +1 -1
  197. package/src/bridge.ts +25 -2
  198. package/src/hooks.ts +229 -16
  199. package/src/init.ts +15 -1
  200. package/src/lib/prompt_safety.ts +88 -1
  201. package/utf8_boot.py +237 -0
  202. package/zcode/AGENTS.md +10 -10
@@ -1,285 +1,285 @@
1
- # -*- coding: utf-8 -*-
2
- """M6 蜂巢 ingest 专项测试:HiveJobsSource 事件映射 / 终态补位 / watermark 幂等 /
3
- 层归属纪律(§5.5 只落 contextual)/ fix-pair 产出(设计稿 §5.6 验收判据)。
4
-
5
- 运行:python -m md_cg.test_hive_ingest (退出码 0 = 全绿)
6
- """
7
- from __future__ import annotations
8
-
9
- import json
10
- import os
11
- import shutil
12
- import sys
13
- import tempfile
14
- import time
15
-
16
- try:
17
- sys.stdout.reconfigure(encoding="utf-8", errors="replace")
18
- except AttributeError:
19
- pass
20
-
21
- from .mdcos import MdCGOS
22
- from .sources import HiveJobsSource, Ingestor, run as sources_run
23
-
24
- PASS, FAIL = 0, 0
25
-
26
-
27
- def check(name, cond, detail=""):
28
- global PASS, FAIL
29
- if cond:
30
- PASS += 1
31
- print(f" [ok] {name}")
32
- else:
33
- FAIL += 1
34
- print(f" [FAIL] {name} {detail}")
35
-
36
-
37
- def make_jobs(root: str):
38
- """构造设计稿 §5.6 验收场景:job1 强杀(无 final,result=error)、
39
- job2 重试成功(final + result=done,content 含命令行)、job3 普通 done。"""
40
- jobs = os.path.join(root, "jobs")
41
- t = time.time()
42
- # job1:强杀——progress 有 start/error,无 final;result=error
43
- j1 = os.path.join(jobs, "h1000000000000_aaa1")
44
- os.makedirs(j1)
45
- json.dump({"model": "cmd", "user_prompt": "跑一次会失败的检查"},
46
- open(os.path.join(j1, "spec.json"), "w", encoding="utf-8"),
47
- ensure_ascii=False)
48
- with open(os.path.join(j1, "progress.jsonl"), "w", encoding="utf-8") as f:
49
- f.write(json.dumps({"ts": t - 60, "kind": "start",
50
- "task": "跑一次会失败的检查"}, ensure_ascii=False) + "\n")
51
- f.write(json.dumps({"ts": t - 30, "kind": "error",
52
- "error": "单步超时(5s)被强杀:pytest"}, ensure_ascii=False) + "\n")
53
- json.dump({"ok": False, "error": "单步超时(5s)被强杀:pytest",
54
- "finished_ts": t - 29},
55
- open(os.path.join(j1, "result.json"), "w", encoding="utf-8"),
56
- ensure_ascii=False)
57
- # job2:重试成功——final + result=done,content 是命令输出(_FIX_RE 命中)
58
- j2 = os.path.join(jobs, "h1000000001000_bbb2")
59
- os.makedirs(j2)
60
- json.dump({"model": "cmd", "user_prompt": "跑一次会失败的检查"},
61
- open(os.path.join(j2, "spec.json"), "w", encoding="utf-8"),
62
- ensure_ascii=False)
63
- with open(os.path.join(j2, "progress.jsonl"), "w", encoding="utf-8") as f:
64
- f.write(json.dumps({"ts": t - 10, "kind": "start",
65
- "task": "跑一次会失败的检查"}, ensure_ascii=False) + "\n")
66
- f.write(json.dumps({"ts": t - 5, "kind": "final",
67
- "content_head": "pytest 全部通过"}, ensure_ascii=False) + "\n")
68
- json.dump({"ok": True, "content": "python hive/test_x.py\n全部通过",
69
- "finished_ts": t - 4},
70
- open(os.path.join(j2, "result.json"), "w", encoding="utf-8"),
71
- ensure_ascii=False)
72
- # job3:普通 done(带 handoff,验证续跑卡映射)
73
- j3 = os.path.join(jobs, "h1000000002000_ccc3")
74
- os.makedirs(j3)
75
- json.dump({"model": "deepseek-flash", "user_prompt": "普通任务"},
76
- open(os.path.join(j3, "spec.json"), "w", encoding="utf-8"),
77
- ensure_ascii=False)
78
- with open(os.path.join(j3, "progress.jsonl"), "w", encoding="utf-8") as f:
79
- f.write(json.dumps({"ts": t - 3, "kind": "start", "task": "普通任务"},
80
- ensure_ascii=False) + "\n")
81
- f.write(json.dumps({"ts": t - 2, "kind": "handoff",
82
- "summary": "上下文将满,交回续跑"}, ensure_ascii=False) + "\n")
83
- json.dump({"ok": True, "content": "普通完成", "finished_ts": t - 1},
84
- open(os.path.join(j3, "result.json"), "w", encoding="utf-8"),
85
- ensure_ascii=False)
86
- # job4:无 finished_ts(批次8 实测缺陷——exec_cmd 旧版形态)→ mtime 兜底
87
- j4 = os.path.join(jobs, "h1000000003000_ddd4")
88
- os.makedirs(j4, exist_ok=True)
89
- json.dump({"model": "cmd", "user_prompt": "legacy"}, open(
90
- os.path.join(j4, "spec.json"), "w", encoding="utf-8"))
91
- rp4 = os.path.join(j4, "result.json")
92
- json.dump({"ok": True, "content": "旧版产物", "steps": [
93
- {"command": ["python", "-c", "print('legacy-ok')"]}]},
94
- open(rp4, "w", encoding="utf-8"), ensure_ascii=False)
95
- os.utime(rp4, (t - 0.5, t - 0.5)) # mtime 锚定到事件时间线内
96
- return jobs
97
-
98
-
99
- def main():
100
- root = tempfile.mkdtemp(prefix="mdcg_m6_")
101
- try:
102
- jobs = make_jobs(root)
103
- cg = MdCGOS(root)
104
- src = HiveJobsSource(jobs)
105
- evs = list(src.events())
106
-
107
- # ---- 1. 事件映射(§5.3) ----
108
- print("[1] 事件映射")
109
- check("1a start → 任务开始(role=user)",
110
- any(e["role"] == "user" and "任务开始" in e["text"] for e in evs))
111
- check("1b tool 条目被跳过(防流水账)",
112
- all("tool_trace" not in e["text"] for e in evs))
113
- check("1c 强杀 job 的 error 事件带「任务失败」前缀(fix-pair 前件)",
114
- any("任务失败" in e["text"] and "aaa1" in e["text"] for e in evs))
115
- check("1d handoff → 续跑卡",
116
- any("续跑卡" in e["text"] for e in evs))
117
- check("1e session=hive:<job_id>",
118
- all(e["session"].startswith("hive:h") for e in evs), str(evs[0]["session"]))
119
-
120
- # ---- 2. 终态补位(§5.6 判据 3) ----
121
- print("[2] result 终态补位")
122
- check("2a 强杀 job(progress 无 final)→ result 终态补位事件存在",
123
- any("aaa1" in e["text"] and "任务失败" in e["text"] for e in evs))
124
- check("2b 补位事件标注 progress 缺 final",
125
- any("result 终态补位" in e["text"] for e in evs))
126
-
127
- # ---- 3. ingest:层归属(§5.5)+ 产出 ----
128
- print("[3] ingest 层归属(只落 contextual,knowledge 零污染)")
129
- k_before = len([n for n, e in (cg.index.get("nodes") or {}).items()
130
- if (e or {}).get("layer") == "knowledge"])
131
- ing = Ingestor(cg, layer="contextual", sensitivity="internal")
132
- # §5.5 硬纪律(与 M3.2 双轨制同构):ingest 自动 mine_fix_pairs 会把
133
- # 挖掘产物直写 knowledge 层(实测 0→2 污染)——M6 路径默认关闭;
134
- # fix-pair 挖掘保留为显式能力(挖掘产物入 knowledge 须走收口)
135
- rep = ing.ingest(src, mine_fix_pairs=False)
136
- check("3a 事件全部写入 contextual", rep["written"] == len(evs),
137
- f"written={rep['written']} evs={len(evs)}")
138
- check("3a2 无 finished_ts 的 result 经 mtime 兜底摄入(批次8 缺陷修复)",
139
- any("legacy-ok" in (x.get("text") or "") for x in evs),
140
- str([x.get("text", "")[:60] for x in evs if "legacy" in x.get("text", "")]))
141
- k_after = len([n for n, e in (cg.index.get("nodes") or {}).items()
142
- if (e or {}).get("layer") == "knowledge"])
143
- check("3b knowledge 层节点数不变(§5.5 反向对照)", k_before == k_after,
144
- f"{k_before}→{k_after}")
145
-
146
- # ---- 4. watermark 幂等(§5.6 判据 2) ----
147
- print("[4] watermark 幂等")
148
- rep2 = ing.ingest(HiveJobsSource(jobs))
149
- check("4a 重跑零新增(断点续跑无重复)",
150
- rep2["new_events"] == 0 and rep2["written"] == 0, str(rep2)[:150])
151
-
152
- # ---- 5. fix-pair(§5.6 判据 1:错误→修复,显式能力验证) ----
153
- print("[5] fix-pair(显式挖掘能力;M6 自动路径已按 §5.5 关闭)")
154
- rep3 = ing.ingest(HiveJobsSource(jobs), mine_fix_pairs=False)
155
- check("5a 幂等重跑下不再产出事件(fix-pair 无重复输入)",
156
- rep3["new_events"] == 0)
157
- # 事件流支持配对挖掘(错误后 4 条内出现命令行 → 配对)——能力验证:
158
- # 实际启用须走收口(编排者/设计者裁决后显式调用,产物才可入 knowledge)
159
- seq = [{"role": e["role"], "text": e["text"]} for e in evs]
160
- fp = cg.mine_fix_pairs(seq)
161
- check("5b 错误→修复对可被挖出(超时失败→重试通过)",
162
- len(fp.get("pairs") or []) >= 1, str(fp)[:200])
163
-
164
- # ---- 6. MCP/CLI 暴露口(sources.run action=hive)+ 事件级密级(双态) ----
165
- print("[6] 暴露口 sources.run(action=hive) + error 事件 private(双态)")
166
- root2 = tempfile.mkdtemp(prefix="mdcg_m6b_")
167
- try:
168
- jobs2 = make_jobs(root2)
169
- # 6a-6c:高 clearance(secret)调用方 → error 事件正常落 private
170
- from .mdcos import MdCGSecure
171
- from .security import Principal
172
- cg_sec = MdCGSecure(root2, principal=Principal(
173
- actor="t_secret", clearance="secret", can_write=True,
174
- role="designer", auth_mode="test"))
175
- rep_h = sources_run(cg_sec, action="hive", path=jobs2)
176
- check("6a run(action=hive) 摄取成功(统一暴露口)",
177
- rep_h.get("written", 0) > 0 and rep_h.get("source", "").startswith(
178
- "hive_jobs:"), str(rep_h)[:150])
179
- priv = inter = 0
180
- for nid, e in (cg_sec.index.get("nodes") or {}).items():
181
- if not str(nid).startswith("src_"):
182
- continue
183
- fm = e or {}
184
- if fm.get("sensitivity") == "private":
185
- priv += 1
186
- elif fm.get("layer") == "contextual":
187
- inter += 1
188
- check("6b error 事件节点=private(secret clearance 落盘成功)",
189
- priv >= 2, f"private={priv}")
190
- check("6c 非 error 事件保持 internal",
191
- inter >= 3, f"internal={inter}")
192
- # 6d-6e:internal clearance 调用方 → private 写入 AccessDenied,
193
- # denied 明细可见(不静默丢失,误差归因原料可查可重放)
194
- cg_int = MdCGSecure(tempfile.mkdtemp(prefix="mdcg_m6c_"))
195
- rep_i = sources_run(cg_int, action="hive", path=jobs2)
196
- check("6d internal clearance 写 private 被拒且明细可见",
197
- rep_i.get("denied", 0) >= 2
198
- and len(rep_i.get("denied_events") or []) == rep_i["denied"],
199
- str(rep_i.get("denied_events"))[:160])
200
- # 6f:显式降级 error_sensitivity=internal → 全部落盘(显式权衡)
201
- cg_low = MdCGSecure(tempfile.mkdtemp(prefix="mdcg_m6d_"))
202
- rep_l = sources_run(cg_low, action="hive", path=jobs2,
203
- error_sensitivity="internal")
204
- check("6f 显式降级 error_sensitivity=internal 全部落盘",
205
- rep_l.get("denied", 0) == 0 and rep_l.get("written", 0) > 0,
206
- str({k: rep_l.get(k) for k in ("written", "denied")}))
207
- # fail-closed:无 path 无 env → 人话指引
208
- os.environ.pop("MDCG_HIVE_JOBS", None)
209
- rep_nc = sources_run(cg_low, action="hive")
210
- check("6g 无 root 时 fail-closed 人话指引(不猜布局)",
211
- rep_nc.get("ok") is False and "MDCG_HIVE_JOBS" in rep_nc["error"])
212
- # dry_run:只统计不写盘
213
- rep_d = sources_run(cg_low, action="hive", path=jobs2, dry_run=True)
214
- check("6h dry_run 预演不写盘",
215
- rep_d.get("dry_run") is True and rep_d.get("written", 1) == 0)
216
- finally:
217
- shutil.rmtree(root2, ignore_errors=True)
218
-
219
- # ---- 7. §5.5 系统纪律化(zcode 外评 break#2/P1):默认不挖矿 + 产物入队 ----
220
- print("[7] mine_fix_pairs 默认翻转 + 自动产物走 propose 队列")
221
- root3 = tempfile.mkdtemp(prefix="mdcg_m7_")
222
- try:
223
- # 造一个含「错误→修复」对的 jsonl 会话文件
224
- jl = os.path.join(root3, "sess.jsonl")
225
- with open(jl, "w", encoding="utf-8") as f:
226
- f.write(json.dumps({"role": "assistant",
227
- "text": "执行失败:ModuleNotFoundError: no x"}) + "\n")
228
- # 修复行必须是行首命令(_FIX_RE 行首启发式)
229
- f.write(json.dumps({"role": "assistant",
230
- "text": "执行修复:\npip install x"}) + "\n")
231
- cg3 = MdCGOS(root3)
232
- kn_before = sum(1 for e in (cg3.index.get("nodes") or {}).values()
233
- if (e or {}).get("layer") == "knowledge")
234
- # 7a:不传 mine_fix_pairs(吃新默认 False)→ 不挖矿
235
- rep_a = sources_run(cg3, action="jsonl", path=jl)
236
- check("7a 默认不挖矿(file/jsonl 不再直写知识层——旧行为即红)",
237
- "fix_pairs" not in rep_a, str(rep_a.get("fix_pairs"))[:120])
238
- kn_after = sum(1 for e in (cg3.index.get("nodes") or {}).values()
239
- if (e or {}).get("layer") == "knowledge")
240
- check("7b 默认路径 knowledge 层零新增(污染反向对照)",
241
- kn_after == kn_before, f"{kn_before} -> {kn_after}")
242
- # 7c:显式开启 → 自动产物走 propose 队列(不直写)
243
- from .sources import JsonlSource
244
- rep_c = Ingestor(cg3).ingest(JsonlSource(jl), mine_fix_pairs=True,
245
- dry_run=True)
246
- # dry_run 不挖矿;改真实写(换新 root 避免水位吃掉事件)
247
- cg4 = MdCGOS(tempfile.mkdtemp(prefix="mdcg_m7b_"))
248
- rep_c = Ingestor(cg4).ingest(JsonlSource(jl), mine_fix_pairs=True)
249
- fp = rep_c.get("fix_pairs") or {}
250
- check("7c 显式开启时自动产物走提案(as_proposals=True,pids 非空)",
251
- fp.get("as_proposals") is True and len(fp.get("pids") or []) >= 1
252
- and not fp.get("knowledge_ids"), str(fp)[:160])
253
- kn_c = sum(1 for e in (cg4.index.get("nodes") or {}).values()
254
- if (e or {}).get("layer") == "knowledge")
255
- check("7d 提案模式下 knowledge 层仍零新增(收口前不入层)",
256
- kn_c == 0, f"knowledge={kn_c}")
257
- check("7e 提案入审核队列(review_list 可见)",
258
- len(cg4.review_list() or []) >= 1,
259
- str(cg4.review_list())[:120])
260
- # 7f:显式工具语义直调(不带 as_proposals)→ 保持直写
261
- r_tools = cg4.mine_fix_pairs([
262
- {"error": "ModuleNotFoundError: no y", "fix": "pip install y"}])
263
- check("7f 显式调用保持直写(knowledge_ids 非空)",
264
- len(r_tools.get("knowledge_ids") or []) == 1, str(r_tools)[:120])
265
- # 7g:payload_hash 幂等——同内容再入队返回原 pid
266
- r2 = cg4.mine_fix_pairs([
267
- {"error": "ModuleNotFoundError: no x", "fix": "pip install x"}],
268
- as_proposals=True)
269
- r3 = cg4.mine_fix_pairs([
270
- {"error": "ModuleNotFoundError: no x", "fix": "pip install x"}],
271
- as_proposals=True)
272
- check("7g 同内容重试幂等(返回原 pid 不重复入队)",
273
- r2["pids"] == r3["pids"] and len(r2["pids"]) >= 1,
274
- f"{r2['pids']} vs {r3['pids']}")
275
- finally:
276
- shutil.rmtree(root3, ignore_errors=True)
277
- finally:
278
- shutil.rmtree(root, ignore_errors=True)
279
-
280
- print(f"\n=== hive ingest tests: {PASS} passed, {FAIL} failed ===")
281
- sys.exit(1 if FAIL else 0)
282
-
283
-
284
- if __name__ == "__main__":
285
- sys.exit(main())
1
+ # -*- coding: utf-8 -*-
2
+ """M6 蜂巢 ingest 专项测试:HiveJobsSource 事件映射 / 终态补位 / watermark 幂等 /
3
+ 层归属纪律(§5.5 只落 contextual)/ fix-pair 产出(设计稿 §5.6 验收判据)。
4
+
5
+ 运行:python -m md_cg.test_hive_ingest (退出码 0 = 全绿)
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import os
11
+ import shutil
12
+ import sys
13
+ import tempfile
14
+ import time
15
+
16
+ try:
17
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
18
+ except AttributeError:
19
+ pass
20
+
21
+ from .mdcos import MdCGOS
22
+ from .sources import HiveJobsSource, Ingestor, run as sources_run
23
+
24
+ PASS, FAIL = 0, 0
25
+
26
+
27
+ def check(name, cond, detail=""):
28
+ global PASS, FAIL
29
+ if cond:
30
+ PASS += 1
31
+ print(f" [ok] {name}")
32
+ else:
33
+ FAIL += 1
34
+ print(f" [FAIL] {name} {detail}")
35
+
36
+
37
+ def make_jobs(root: str):
38
+ """构造设计稿 §5.6 验收场景:job1 强杀(无 final,result=error)、
39
+ job2 重试成功(final + result=done,content 含命令行)、job3 普通 done。"""
40
+ jobs = os.path.join(root, "jobs")
41
+ t = time.time()
42
+ # job1:强杀——progress 有 start/error,无 final;result=error
43
+ j1 = os.path.join(jobs, "h1000000000000_aaa1")
44
+ os.makedirs(j1)
45
+ json.dump({"model": "cmd", "user_prompt": "跑一次会失败的检查"},
46
+ open(os.path.join(j1, "spec.json"), "w", encoding="utf-8"),
47
+ ensure_ascii=False)
48
+ with open(os.path.join(j1, "progress.jsonl"), "w", encoding="utf-8") as f:
49
+ f.write(json.dumps({"ts": t - 60, "kind": "start",
50
+ "task": "跑一次会失败的检查"}, ensure_ascii=False) + "\n")
51
+ f.write(json.dumps({"ts": t - 30, "kind": "error",
52
+ "error": "单步超时(5s)被强杀:pytest"}, ensure_ascii=False) + "\n")
53
+ json.dump({"ok": False, "error": "单步超时(5s)被强杀:pytest",
54
+ "finished_ts": t - 29},
55
+ open(os.path.join(j1, "result.json"), "w", encoding="utf-8"),
56
+ ensure_ascii=False)
57
+ # job2:重试成功——final + result=done,content 是命令输出(_FIX_RE 命中)
58
+ j2 = os.path.join(jobs, "h1000000001000_bbb2")
59
+ os.makedirs(j2)
60
+ json.dump({"model": "cmd", "user_prompt": "跑一次会失败的检查"},
61
+ open(os.path.join(j2, "spec.json"), "w", encoding="utf-8"),
62
+ ensure_ascii=False)
63
+ with open(os.path.join(j2, "progress.jsonl"), "w", encoding="utf-8") as f:
64
+ f.write(json.dumps({"ts": t - 10, "kind": "start",
65
+ "task": "跑一次会失败的检查"}, ensure_ascii=False) + "\n")
66
+ f.write(json.dumps({"ts": t - 5, "kind": "final",
67
+ "content_head": "pytest 全部通过"}, ensure_ascii=False) + "\n")
68
+ json.dump({"ok": True, "content": "python hive/test_x.py\n全部通过",
69
+ "finished_ts": t - 4},
70
+ open(os.path.join(j2, "result.json"), "w", encoding="utf-8"),
71
+ ensure_ascii=False)
72
+ # job3:普通 done(带 handoff,验证续跑卡映射)
73
+ j3 = os.path.join(jobs, "h1000000002000_ccc3")
74
+ os.makedirs(j3)
75
+ json.dump({"model": "deepseek-flash", "user_prompt": "普通任务"},
76
+ open(os.path.join(j3, "spec.json"), "w", encoding="utf-8"),
77
+ ensure_ascii=False)
78
+ with open(os.path.join(j3, "progress.jsonl"), "w", encoding="utf-8") as f:
79
+ f.write(json.dumps({"ts": t - 3, "kind": "start", "task": "普通任务"},
80
+ ensure_ascii=False) + "\n")
81
+ f.write(json.dumps({"ts": t - 2, "kind": "handoff",
82
+ "summary": "上下文将满,交回续跑"}, ensure_ascii=False) + "\n")
83
+ json.dump({"ok": True, "content": "普通完成", "finished_ts": t - 1},
84
+ open(os.path.join(j3, "result.json"), "w", encoding="utf-8"),
85
+ ensure_ascii=False)
86
+ # job4:无 finished_ts(批次8 实测缺陷——exec_cmd 旧版形态)→ mtime 兜底
87
+ j4 = os.path.join(jobs, "h1000000003000_ddd4")
88
+ os.makedirs(j4, exist_ok=True)
89
+ json.dump({"model": "cmd", "user_prompt": "legacy"}, open(
90
+ os.path.join(j4, "spec.json"), "w", encoding="utf-8"))
91
+ rp4 = os.path.join(j4, "result.json")
92
+ json.dump({"ok": True, "content": "旧版产物", "steps": [
93
+ {"command": ["python", "-c", "print('legacy-ok')"]}]},
94
+ open(rp4, "w", encoding="utf-8"), ensure_ascii=False)
95
+ os.utime(rp4, (t - 0.5, t - 0.5)) # mtime 锚定到事件时间线内
96
+ return jobs
97
+
98
+
99
+ def main():
100
+ root = tempfile.mkdtemp(prefix="mdcg_m6_")
101
+ try:
102
+ jobs = make_jobs(root)
103
+ cg = MdCGOS(root)
104
+ src = HiveJobsSource(jobs)
105
+ evs = list(src.events())
106
+
107
+ # ---- 1. 事件映射(§5.3) ----
108
+ print("[1] 事件映射")
109
+ check("1a start → 任务开始(role=user)",
110
+ any(e["role"] == "user" and "任务开始" in e["text"] for e in evs))
111
+ check("1b tool 条目被跳过(防流水账)",
112
+ all("tool_trace" not in e["text"] for e in evs))
113
+ check("1c 强杀 job 的 error 事件带「任务失败」前缀(fix-pair 前件)",
114
+ any("任务失败" in e["text"] and "aaa1" in e["text"] for e in evs))
115
+ check("1d handoff → 续跑卡",
116
+ any("续跑卡" in e["text"] for e in evs))
117
+ check("1e session=hive:<job_id>",
118
+ all(e["session"].startswith("hive:h") for e in evs), str(evs[0]["session"]))
119
+
120
+ # ---- 2. 终态补位(§5.6 判据 3) ----
121
+ print("[2] result 终态补位")
122
+ check("2a 强杀 job(progress 无 final)→ result 终态补位事件存在",
123
+ any("aaa1" in e["text"] and "任务失败" in e["text"] for e in evs))
124
+ check("2b 补位事件标注 progress 缺 final",
125
+ any("result 终态补位" in e["text"] for e in evs))
126
+
127
+ # ---- 3. ingest:层归属(§5.5)+ 产出 ----
128
+ print("[3] ingest 层归属(只落 contextual,knowledge 零污染)")
129
+ k_before = len([n for n, e in list((cg.index.get("nodes") or {}).items())
130
+ if (e or {}).get("layer") == "knowledge"])
131
+ ing = Ingestor(cg, layer="contextual", sensitivity="internal")
132
+ # §5.5 硬纪律(与 M3.2 双轨制同构):ingest 自动 mine_fix_pairs 会把
133
+ # 挖掘产物直写 knowledge 层(实测 0→2 污染)——M6 路径默认关闭;
134
+ # fix-pair 挖掘保留为显式能力(挖掘产物入 knowledge 须走收口)
135
+ rep = ing.ingest(src, mine_fix_pairs=False)
136
+ check("3a 事件全部写入 contextual", rep["written"] == len(evs),
137
+ f"written={rep['written']} evs={len(evs)}")
138
+ check("3a2 无 finished_ts 的 result 经 mtime 兜底摄入(批次8 缺陷修复)",
139
+ any("legacy-ok" in (x.get("text") or "") for x in evs),
140
+ str([x.get("text", "")[:60] for x in evs if "legacy" in x.get("text", "")]))
141
+ k_after = len([n for n, e in list((cg.index.get("nodes") or {}).items())
142
+ if (e or {}).get("layer") == "knowledge"])
143
+ check("3b knowledge 层节点数不变(§5.5 反向对照)", k_before == k_after,
144
+ f"{k_before}→{k_after}")
145
+
146
+ # ---- 4. watermark 幂等(§5.6 判据 2) ----
147
+ print("[4] watermark 幂等")
148
+ rep2 = ing.ingest(HiveJobsSource(jobs))
149
+ check("4a 重跑零新增(断点续跑无重复)",
150
+ rep2["new_events"] == 0 and rep2["written"] == 0, str(rep2)[:150])
151
+
152
+ # ---- 5. fix-pair(§5.6 判据 1:错误→修复,显式能力验证) ----
153
+ print("[5] fix-pair(显式挖掘能力;M6 自动路径已按 §5.5 关闭)")
154
+ rep3 = ing.ingest(HiveJobsSource(jobs), mine_fix_pairs=False)
155
+ check("5a 幂等重跑下不再产出事件(fix-pair 无重复输入)",
156
+ rep3["new_events"] == 0)
157
+ # 事件流支持配对挖掘(错误后 4 条内出现命令行 → 配对)——能力验证:
158
+ # 实际启用须走收口(编排者/设计者裁决后显式调用,产物才可入 knowledge)
159
+ seq = [{"role": e["role"], "text": e["text"]} for e in evs]
160
+ fp = cg.mine_fix_pairs(seq)
161
+ check("5b 错误→修复对可被挖出(超时失败→重试通过)",
162
+ len(fp.get("pairs") or []) >= 1, str(fp)[:200])
163
+
164
+ # ---- 6. MCP/CLI 暴露口(sources.run action=hive)+ 事件级密级(双态) ----
165
+ print("[6] 暴露口 sources.run(action=hive) + error 事件 private(双态)")
166
+ root2 = tempfile.mkdtemp(prefix="mdcg_m6b_")
167
+ try:
168
+ jobs2 = make_jobs(root2)
169
+ # 6a-6c:高 clearance(secret)调用方 → error 事件正常落 private
170
+ from .mdcos import MdCGSecure
171
+ from .security import Principal
172
+ cg_sec = MdCGSecure(root2, principal=Principal(
173
+ actor="t_secret", clearance="secret", can_write=True,
174
+ role="designer", auth_mode="test"))
175
+ rep_h = sources_run(cg_sec, action="hive", path=jobs2)
176
+ check("6a run(action=hive) 摄取成功(统一暴露口)",
177
+ rep_h.get("written", 0) > 0 and rep_h.get("source", "").startswith(
178
+ "hive_jobs:"), str(rep_h)[:150])
179
+ priv = inter = 0
180
+ for nid, e in list((cg_sec.index.get("nodes") or {}).items()):
181
+ if not str(nid).startswith("src_"):
182
+ continue
183
+ fm = e or {}
184
+ if fm.get("sensitivity") == "private":
185
+ priv += 1
186
+ elif fm.get("layer") == "contextual":
187
+ inter += 1
188
+ check("6b error 事件节点=private(secret clearance 落盘成功)",
189
+ priv >= 2, f"private={priv}")
190
+ check("6c 非 error 事件保持 internal",
191
+ inter >= 3, f"internal={inter}")
192
+ # 6d-6e:internal clearance 调用方 → private 写入 AccessDenied,
193
+ # denied 明细可见(不静默丢失,误差归因原料可查可重放)
194
+ cg_int = MdCGSecure(tempfile.mkdtemp(prefix="mdcg_m6c_"))
195
+ rep_i = sources_run(cg_int, action="hive", path=jobs2)
196
+ check("6d internal clearance 写 private 被拒且明细可见",
197
+ rep_i.get("denied", 0) >= 2
198
+ and len(rep_i.get("denied_events") or []) == rep_i["denied"],
199
+ str(rep_i.get("denied_events"))[:160])
200
+ # 6f:显式降级 error_sensitivity=internal → 全部落盘(显式权衡)
201
+ cg_low = MdCGSecure(tempfile.mkdtemp(prefix="mdcg_m6d_"))
202
+ rep_l = sources_run(cg_low, action="hive", path=jobs2,
203
+ error_sensitivity="internal")
204
+ check("6f 显式降级 error_sensitivity=internal 全部落盘",
205
+ rep_l.get("denied", 0) == 0 and rep_l.get("written", 0) > 0,
206
+ str({k: rep_l.get(k) for k in ("written", "denied")}))
207
+ # fail-closed:无 path 无 env → 人话指引
208
+ os.environ.pop("MDCG_HIVE_JOBS", None)
209
+ rep_nc = sources_run(cg_low, action="hive")
210
+ check("6g 无 root 时 fail-closed 人话指引(不猜布局)",
211
+ rep_nc.get("ok") is False and "MDCG_HIVE_JOBS" in rep_nc["error"])
212
+ # dry_run:只统计不写盘
213
+ rep_d = sources_run(cg_low, action="hive", path=jobs2, dry_run=True)
214
+ check("6h dry_run 预演不写盘",
215
+ rep_d.get("dry_run") is True and rep_d.get("written", 1) == 0)
216
+ finally:
217
+ shutil.rmtree(root2, ignore_errors=True)
218
+
219
+ # ---- 7. §5.5 系统纪律化(zcode 外评 break#2/P1):默认不挖矿 + 产物入队 ----
220
+ print("[7] mine_fix_pairs 默认翻转 + 自动产物走 propose 队列")
221
+ root3 = tempfile.mkdtemp(prefix="mdcg_m7_")
222
+ try:
223
+ # 造一个含「错误→修复」对的 jsonl 会话文件
224
+ jl = os.path.join(root3, "sess.jsonl")
225
+ with open(jl, "w", encoding="utf-8") as f:
226
+ f.write(json.dumps({"role": "assistant",
227
+ "text": "执行失败:ModuleNotFoundError: no x"}) + "\n")
228
+ # 修复行必须是行首命令(_FIX_RE 行首启发式)
229
+ f.write(json.dumps({"role": "assistant",
230
+ "text": "执行修复:\npip install x"}) + "\n")
231
+ cg3 = MdCGOS(root3)
232
+ kn_before = sum(1 for e in list((cg3.index.get("nodes") or {}).values())
233
+ if (e or {}).get("layer") == "knowledge")
234
+ # 7a:不传 mine_fix_pairs(吃新默认 False)→ 不挖矿
235
+ rep_a = sources_run(cg3, action="jsonl", path=jl)
236
+ check("7a 默认不挖矿(file/jsonl 不再直写知识层——旧行为即红)",
237
+ "fix_pairs" not in rep_a, str(rep_a.get("fix_pairs"))[:120])
238
+ kn_after = sum(1 for e in list((cg3.index.get("nodes") or {}).values())
239
+ if (e or {}).get("layer") == "knowledge")
240
+ check("7b 默认路径 knowledge 层零新增(污染反向对照)",
241
+ kn_after == kn_before, f"{kn_before} -> {kn_after}")
242
+ # 7c:显式开启 → 自动产物走 propose 队列(不直写)
243
+ from .sources import JsonlSource
244
+ rep_c = Ingestor(cg3).ingest(JsonlSource(jl), mine_fix_pairs=True,
245
+ dry_run=True)
246
+ # dry_run 不挖矿;改真实写(换新 root 避免水位吃掉事件)
247
+ cg4 = MdCGOS(tempfile.mkdtemp(prefix="mdcg_m7b_"))
248
+ rep_c = Ingestor(cg4).ingest(JsonlSource(jl), mine_fix_pairs=True)
249
+ fp = rep_c.get("fix_pairs") or {}
250
+ check("7c 显式开启时自动产物走提案(as_proposals=True,pids 非空)",
251
+ fp.get("as_proposals") is True and len(fp.get("pids") or []) >= 1
252
+ and not fp.get("knowledge_ids"), str(fp)[:160])
253
+ kn_c = sum(1 for e in list((cg4.index.get("nodes") or {}).values())
254
+ if (e or {}).get("layer") == "knowledge")
255
+ check("7d 提案模式下 knowledge 层仍零新增(收口前不入层)",
256
+ kn_c == 0, f"knowledge={kn_c}")
257
+ check("7e 提案入审核队列(review_list 可见)",
258
+ len(cg4.review_list() or []) >= 1,
259
+ str(cg4.review_list())[:120])
260
+ # 7f:显式工具语义直调(不带 as_proposals)→ 保持直写
261
+ r_tools = cg4.mine_fix_pairs([
262
+ {"error": "ModuleNotFoundError: no y", "fix": "pip install y"}])
263
+ check("7f 显式调用保持直写(knowledge_ids 非空)",
264
+ len(r_tools.get("knowledge_ids") or []) == 1, str(r_tools)[:120])
265
+ # 7g:payload_hash 幂等——同内容再入队返回原 pid
266
+ r2 = cg4.mine_fix_pairs([
267
+ {"error": "ModuleNotFoundError: no x", "fix": "pip install x"}],
268
+ as_proposals=True)
269
+ r3 = cg4.mine_fix_pairs([
270
+ {"error": "ModuleNotFoundError: no x", "fix": "pip install x"}],
271
+ as_proposals=True)
272
+ check("7g 同内容重试幂等(返回原 pid 不重复入队)",
273
+ r2["pids"] == r3["pids"] and len(r2["pids"]) >= 1,
274
+ f"{r2['pids']} vs {r3['pids']}")
275
+ finally:
276
+ shutil.rmtree(root3, ignore_errors=True)
277
+ finally:
278
+ shutil.rmtree(root, ignore_errors=True)
279
+
280
+ print(f"\n=== hive ingest tests: {PASS} passed, {FAIL} failed ===")
281
+ sys.exit(1 if FAIL else 0)
282
+
283
+
284
+ if __name__ == "__main__":
285
+ sys.exit(main())