@furongjun1999/dsh-memory 0.4.7 → 0.4.9

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 (325) hide show
  1. package/README.md +130 -47
  2. package/codebuddy/CODEBUDDY.md +21 -10
  3. package/codebuddy/README.md +13 -1
  4. package/codebuddy/mcp.json +9 -0
  5. package/docs/GBrain/345/217/257/345/200/237/351/211/264/347/202/271_/347/201/265/346/236/242/350/220/275/347/202/271/344/272/244/346/216/245_20260919.md +169 -0
  6. package/docs/Pi/345/217/257/345/255/246/344/271/240/344/274/230/347/202/271_/347/201/265/346/236/242/345/244/247/350/204/221/346/224/271/350/277/233/344/272/244/346/216/245_20260915.md +144 -0
  7. package/docs/README.md +111 -0
  8. package/docs/discipline/harnesses.yaml +226 -152
  9. package/docs/discipline/templates/full.md.tmpl +61 -58
  10. package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_LoCoMo_/347/201/265/346/236/242_.md +127 -0
  11. package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_LoCoMo_/347/201/265/346/236/242_.png +0 -0
  12. package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_/347/201/265/346/236/242_vs_dejavu_/347/273/237/344/270/200/350/257/204/345/210/206_v7.md +202 -0
  13. package/docs/experiments/linkref_backfill/candidates_20260917.json +726 -0
  14. package/docs/experiments/linkref_backfill/candidates_internal_20260917.json +602 -0
  15. package/docs/experiments/linkref_backfill/candidates_internal_v2.json +603 -0
  16. package/docs/experiments/linkref_backfill/candidates_secret_20260917.json +884 -0
  17. package/docs/experiments/linkref_backfill/candidates_secret_v2.json +789 -0
  18. package/docs/experiments/m4-role-probe/census.py +86 -0
  19. package/docs/experiments/m4-role-probe/probe.py +89 -0
  20. package/docs/experiments/mapped_confidence/bench_mapped_conf.py +216 -0
  21. package/docs/experiments/mapped_confidence/post_check.py +75 -0
  22. package/docs/experiments/mapped_confidence/repro_thirdparty_tol.py +83 -0
  23. package/docs/experiments/mapped_confidence/result_mapped_conf.json +215 -0
  24. package/docs/hive//345/244/232/347/253/257harness/351/200/232/344/277/241/345/245/221/347/272/246_v0.1.md +42 -0
  25. package/docs/hive//346/243/200/347/264/242/346/224/266/346/225/233/345/256/236/346/265/213/344/270/216S1b/350/256/276/350/256/241_v0.1.md +43 -0
  26. 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 +102 -0
  27. package/docs/hive//347/234/237/345/256/236/345/272/223/347/253/257/345/210/260/347/253/257/345/256/236/346/265/213_S1b/344/270/216/345/217/254/345/233/236/346/235/203/350/241/241_v0.1.md +57 -0
  28. package/docs/hive//347/234/237/345/256/236/345/272/223/347/253/257/345/210/260/347/253/257/345/256/236/346/265/213_S7/345/200/222/346/216/222/345/200/231/351/200/211/345/261/202_v0.1.md +126 -0
  29. 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 +503 -0
  30. package/docs/{ → hive/}/350/234/202/345/267/242/345/267/245/344/275/234/350/256/260/345/277/206_/351/241/271/347/233/256/350/256/241/345/210/222.md +14 -2
  31. package/docs/mdcg/D_meta_/345/267/245/347/250/213/345/214/226/346/226/271/346/241/210_v0.2.md +216 -0
  32. package/docs/{README → mdcg/README}/350/257/246/347/273/206/347/211/210_v0.4.5.md +631 -625
  33. package/docs/{ → mdcg/}/344/270/273/344/273/243/347/220/206/345/255/220/344/273/243/347/220/206/350/256/260/345/277/206/346/236/266/346/236/204/350/256/276/350/256/241.md +1 -1
  34. package/docs/mdcg//344/273/243/347/240/201/350/257/204/345/256/241/344/270/216/346/235/241/344/273/266/345/214/226/346/263/250/351/207/212_/345/245/221/347/272/246_v0.1.md +82 -0
  35. package/docs/mdcg//345/205/250/345/272/223/344/273/243/347/240/201/350/257/204/345/256/241/344/270/216/346/235/241/344/273/266/345/214/226/346/263/250/351/207/212_/350/256/241/345/210/222_v0.1.md +600 -0
  36. 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 +263 -0
  37. package/docs/{ → mdcg/}/345/215/225/345/205/203/350/207/252/346/210/221/351/224/232/347/202/271_/347/263/273/347/273/237/346/217/220/347/244/272/350/257/215/346/240/207/345/207/206_v0.3.md +1 -1
  38. package/docs/mdcg//345/255/220/344/273/243/347/220/206/351/205/215/347/275/256/346/240/207/345/207/206_v0.5.md +236 -0
  39. package/docs/{ → mdcg/}/346/272/220/347/240/201/347/272/247/346/236/266/346/236/204/345/256/241/350/256/241_GPT/346/211/271/350/257/204/345/257/271/347/205/247_v1.0.md +2 -2
  40. package/docs/{ → mdcg/}/347/201/265/346/236/242/344/270/211/345/261/202/346/213/206/345/210/206/350/247/204/345/210/222_v0.1.md +181 -181
  41. 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 +172 -0
  42. package/docs/mdcg//347/216/257/344/272/214_/347/231/275/347/256/261/345/241/253/345/205/205/346/265/201/346/260/264/347/272/277_v0.1.md +31 -0
  43. package/docs/{ → mdcg/}/347/274/272/345/217/243/345/215/225_P0/346/224/266/345/217/243_v0.1.md +2 -2
  44. package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_G4-G8/347/274/272/345/217/243/350/243/201/345/256/232/345/215/225_v0.1.md +1 -1
  45. 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 +4 -4
  46. package/docs/{ → mdcg/}/350/256/260/345/277/206/346/223/215/344/275/234/347/263/273/347/273/237_MdCGOS/344/270/216MCP/346/216/245/345/205/245_v0.1.md +2 -2
  47. package/docs/mdcg//350/267/250/347/253/257/351/252/214/350/257/201/344/270/216/345/220/214/346/255/245/345/215/217/350/256/256_v0.1.md +93 -0
  48. package/docs/plans/GridWorld/346/234/200/345/260/217/351/227/255/347/216/257/350/247/204/346/240/274_v0.1.md +176 -0
  49. package/docs/{ → swarm/}/350/234/202/347/276/244/344/272/222/350/201/224_v0.1.md +3 -3
  50. package/docs/swarm//350/234/202/347/276/244/345/220/214/351/224/231/346/243/200/346/265/213/345/256/236/351/252/214/345/215/217/350/256/256_v0.1.md +242 -0
  51. package/docs/theory//345/215/225/347/272/277/347/250/213/344/270/216/346/263/250/346/204/217/345/212/233/351/233/206/344/270/255_/346/227/240/344/272/211/350/256/256/347/220/206/350/256/272/346/226/207/346/241/243_v1.0.md +129 -0
  52. package/docs/theory//345/271/266/345/217/221/345/277/205/347/204/266/346/200/247/347/220/206/350/256/272_v0.2.md +134 -0
  53. package/docs/theory//346/246/202/345/277/265/345/210/206/345/261/202/345/257/271/351/275/220/350/241/250_v0.1.md +145 -0
  54. package/docs/{ → theory/}/347/220/206/350/256/272_/346/234/272/345/210/266_/344/273/243/347/240/201_/345/256/236/351/252/214_/347/274/272/345/217/243/347/237/251/351/230/265_v0.1.md +6 -6
  55. package/docs/{ → theory/}/347/220/206/350/256/272/344/273/223/346/213/206/345/210/206/344/270/216/347/231/275/347/256/261/347/237/245/350/257/206/345/272/223/345/206/205/350/277/201_v0.1.md +1 -1
  56. package/docs/theory//350/256/244/347/237/245/344/273/243/347/220/206/344/270/216/346/224/266/346/225/233/347/273/223/346/236/204_/346/235/241/344/273/266/350/256/272/351/207/215/346/236/204_v0.1.md +221 -0
  57. package/docs/world_model//344/270/226/347/225/214/346/250/241/345/236/213_/347/245/236/347/273/217/347/275/221/347/273/234/345/272/225/345/261/202/346/236/266/346/236/204_v1.0.md +237 -0
  58. package/docs//345/255/230/347/256/227/344/270/200/344/275/223/346/236/266/346/236/204/345/255/246/344/271/240_/345/244/226/351/203/250/347/220/206/350/256/272/345/257/271/347/205/247/344/270/216/350/267/257/347/272/277/344/272/244/346/216/245_20260916.md +98 -0
  59. package/docs//345/255/230/347/256/227/344/270/200/344/275/223/347/245/236/347/273/217/347/275/221/347/273/234/346/236/266/346/236/204_/346/200/273/347/272/262/344/270/216/344/272/244/346/216/245_20260916.md +109 -0
  60. 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 +434 -405
  61. package/docs//347/201/265/346/236/242/350/207/252/346/210/221/346/224/271/350/277/233/345/267/245/344/275/234/350/256/241/345/210/222_/345/244/226/351/203/250/347/240/224/347/251/266/347/263/273/345/210/227/345/220/270/346/224/266_v1_20260919.md +286 -0
  62. package/docs//347/231/275/347/256/261/345/214/226/347/272/262/351/242/206_/344/270/211/351/241/271/347/233/256/345/255/246/344/271/240/346/200/273/346/236/266/346/236/204/344/270/216Ghidra/344/272/244/346/216/245_20260916.md +104 -0
  63. package/docs//350/256/260/345/277/206/350/257/204/345/256/241/347/263/273/347/273/237_/347/253/213/351/241/271/350/256/276/350/256/241/344/270/216/346/226/275/345/267/245/344/272/244/346/216/245_20260915.md +183 -0
  64. package/dsh/README.md +34 -1
  65. package/dsh/cordis.yml.example +13 -7
  66. package/dsh/hive-mcp-probe.mjs +94 -0
  67. package/dsh/hive-mcp.example.yml +62 -0
  68. package/dsh/update-lingshu.bat +11 -0
  69. package/dsh/update-lingshu.ps1 +337 -0
  70. package/lib/hooks.d.ts +8 -0
  71. package/lib/hooks.js +27 -24
  72. package/lib/index.d.ts +4 -2
  73. package/lib/index.js +29 -6
  74. package/lib/lib/datapath.d.ts +76 -1
  75. package/lib/lib/datapath.js +199 -13
  76. package/lib/lib/mdcg_client.d.ts +6 -3
  77. package/lib/lib/mdcg_client.js +6 -5
  78. package/lib/lib/mutual.js +4 -4
  79. package/lib/lib/prompt_safety.d.ts +48 -0
  80. package/lib/lib/prompt_safety.js +62 -0
  81. package/lib/lib/token_store.js +4 -5
  82. package/md_cg/audit.py +91 -2
  83. package/md_cg/autonomy.py +86 -15
  84. package/md_cg/backfill.py +36 -1
  85. package/md_cg/backfill_bigdomain.py +34 -0
  86. package/md_cg/bench6_arms.py +28 -1
  87. package/md_cg/bench6_common.py +10 -1
  88. package/md_cg/bench6_competitors.py +6 -1
  89. package/md_cg/bench_axis_domain.py +9 -1
  90. package/md_cg/bench_blind_comp.py +7 -1
  91. package/md_cg/bench_en_atoms_public.py +9 -0
  92. package/md_cg/bench_governance.py +348 -0
  93. package/md_cg/bench_lme_zh.py +16 -1
  94. package/md_cg/bench_locomo.py +2 -1
  95. package/md_cg/bench_locomo_zh.py +16 -1
  96. package/md_cg/bench_locomo_zh_public.py +4 -1
  97. package/md_cg/bench_longmem.py +2 -1
  98. package/md_cg/bench_membench.py +37 -6
  99. package/md_cg/bench_p0.py +4 -1
  100. package/md_cg/bench_progressive.py +61 -10
  101. package/md_cg/bench_role_views.py +238 -0
  102. package/md_cg/bench_task_ab.py +8 -1
  103. package/md_cg/bench_task_ab_llm.py +13 -1
  104. package/md_cg/bench_unified_en.py +6 -1
  105. package/md_cg/bench_zh_mad.py +20 -1
  106. package/md_cg/blindspot_tickets.py +123 -0
  107. package/md_cg/branches.py +286 -0
  108. package/md_cg/build_postings.py +73 -0
  109. package/md_cg/ccgc.py +949 -0
  110. package/md_cg/census.py +5 -1
  111. package/md_cg/chain.py +24 -3
  112. package/md_cg/codeindex.py +134 -17
  113. package/md_cg/coldverify.py +265 -0
  114. package/md_cg/comment_gate.py +338 -0
  115. package/md_cg/cond_compose.py +190 -0
  116. package/md_cg/cond_facts.py +155 -0
  117. package/md_cg/cond_template.json +107 -0
  118. package/md_cg/condition_anchor.py +143 -0
  119. package/md_cg/conformance.py +727 -0
  120. package/md_cg/consistency.py +25 -2
  121. package/md_cg/consolidate.py +53 -2
  122. package/md_cg/corpus.py +5 -1
  123. package/md_cg/crosscheck.py +42 -2
  124. package/md_cg/crypto.py +35 -1
  125. package/md_cg/d_meta.py +310 -0
  126. package/md_cg/datapath.py +201 -26
  127. package/md_cg/docindex.py +122 -1
  128. package/md_cg/eval_common.py +35 -6
  129. package/md_cg/evidence.py +27 -1
  130. package/md_cg/evolution.py +21 -1
  131. package/md_cg/export.py +11 -1
  132. package/md_cg/forgetting.py +34 -4
  133. package/md_cg/fsutil.py +81 -2
  134. package/md_cg/hotcache.py +214 -0
  135. package/md_cg/hyperedge.py +251 -0
  136. package/md_cg/identity.py +20 -3
  137. package/md_cg/insight.py +19 -3
  138. package/md_cg/lexicon/build_cedict_en_zh.py +9 -0
  139. package/md_cg/lexicon/build_standard_en.py +171 -168
  140. package/md_cg/lexicon/expand_en_zh.py +6 -0
  141. package/md_cg/lifecycle.py +273 -0
  142. package/md_cg/linkref.py +281 -0
  143. package/md_cg/links.py +29 -1
  144. package/md_cg/mcp_server.py +894 -168
  145. package/md_cg/md_whitebox.py +53 -1
  146. package/md_cg/mdcg.py +1205 -41
  147. package/md_cg/mdcos.py +1060 -76
  148. package/md_cg/metacognition.py +39 -4
  149. package/md_cg/migrate.py +4 -0
  150. package/md_cg/migrate_aeis.py +221 -213
  151. package/md_cg/migrate_roleplay.py +8 -0
  152. package/md_cg/migrate_wisdom_graph.py +14 -1
  153. package/md_cg/mreview/__init__.py +25 -0
  154. package/md_cg/mreview/__main__.py +110 -0
  155. package/md_cg/mreview/bundle.py +178 -0
  156. package/md_cg/mreview/candidates.py +262 -0
  157. package/md_cg/mreview/govern.py +694 -0
  158. package/md_cg/mreview/locate.py +939 -0
  159. package/md_cg/mreview/pipeline.py +729 -0
  160. package/md_cg/mreview/rules/duplication.json +21 -0
  161. package/md_cg/mreview/rules/field_coverage.json +54 -0
  162. package/md_cg/mreview/rules/source_license.json +21 -0
  163. package/md_cg/mreview/rules/template_flow.json +21 -0
  164. package/md_cg/mreview/ruleset.py +253 -0
  165. package/md_cg/nodefile.py +269 -1
  166. package/md_cg/pooling.py +23 -1
  167. package/md_cg/postings.py +298 -0
  168. package/md_cg/predict.py +113 -15
  169. package/md_cg/progressive.py +3 -0
  170. package/md_cg/protect.py +18 -5
  171. package/md_cg/protocol.py +372 -0
  172. package/md_cg/provenance.py +262 -0
  173. package/md_cg/reach.py +453 -0
  174. package/md_cg/refindex.py +88 -4
  175. package/md_cg/refine.py +20 -1
  176. package/md_cg/roleviews.py +89 -0
  177. package/md_cg/routing.py +76 -0
  178. package/md_cg/scrub.py +68 -3
  179. package/md_cg/security.py +26 -1
  180. package/md_cg/self_state.py +65 -2
  181. package/md_cg/selfreport.py +151 -0
  182. package/md_cg/semantic/canonical.py +5 -0
  183. package/md_cg/semantic/en_normalizer.py +364 -295
  184. package/md_cg/semantic/zh_en_atoms.py +139 -136
  185. package/md_cg/signer.py +41 -1
  186. package/md_cg/sources.py +583 -547
  187. package/md_cg/statushdr.py +179 -0
  188. package/md_cg/stg.py +59 -18
  189. package/md_cg/subgraph.py +23 -0
  190. package/md_cg/sustain.py +74 -1
  191. package/md_cg/tasks.py +471 -0
  192. package/md_cg/test_action_derive.py +203 -0
  193. package/md_cg/test_audit_rotate.py +270 -0
  194. package/md_cg/test_autonomy.py +26 -0
  195. package/md_cg/test_bench_governance.py +102 -0
  196. package/md_cg/test_blindspot_tickets.py +166 -0
  197. package/md_cg/test_branches.py +249 -0
  198. package/md_cg/test_ccg_perturb.py +1 -1
  199. package/md_cg/test_ccgc.py +433 -0
  200. package/md_cg/test_codeindex.py +338 -0
  201. package/md_cg/test_comment_gate.py +187 -0
  202. package/md_cg/test_cond_compose_anchors.py +76 -0
  203. package/md_cg/test_condition_anchor.py +82 -0
  204. package/md_cg/test_conformance.py +343 -0
  205. package/md_cg/test_d_meta.py +412 -0
  206. package/md_cg/test_datapath_root.py +188 -0
  207. package/md_cg/test_en_pipeline.py +24 -0
  208. package/md_cg/test_gain_gate.py +47 -1
  209. package/md_cg/test_health_scale.py +173 -0
  210. package/md_cg/test_hot_cold.py +187 -0
  211. package/md_cg/test_hyperedge.py +245 -0
  212. package/md_cg/test_identity_attribution.py +6 -0
  213. package/md_cg/test_index_durability.py +224 -0
  214. package/md_cg/test_lifecycle.py +309 -0
  215. package/md_cg/test_linkref.py +306 -0
  216. package/md_cg/test_md_access_parity.py +15 -3
  217. package/md_cg/test_mr_m1.py +769 -0
  218. package/md_cg/test_mr_m2.py +587 -0
  219. package/md_cg/test_mr_m3.py +710 -0
  220. package/md_cg/test_mr_m4.py +485 -0
  221. package/md_cg/test_p1.py +1 -1
  222. package/md_cg/test_p11_consistency.py +1 -1
  223. package/md_cg/test_p12_metacognition.py +2 -2
  224. package/md_cg/test_p16_self_state.py +1 -1
  225. package/md_cg/test_p26_refindex.py +50 -21
  226. package/md_cg/test_p27_docindex.py +258 -4
  227. package/md_cg/test_p28_refcheck.py +1 -1
  228. package/md_cg/test_p29_session_ingest_export.py +1 -1
  229. package/md_cg/test_p2_mcp.py +7 -1
  230. package/md_cg/test_p31_insight.py +24 -0
  231. package/md_cg/test_p38_contextualize.py +1 -1
  232. package/md_cg/test_p39_vision_evidence.py +1 -1
  233. package/md_cg/test_p40_refine_worklist.py +1 -1
  234. package/md_cg/test_p41_evolve_patrol.py +1 -1
  235. package/md_cg/test_p42_provenance.py +1 -1
  236. package/md_cg/test_p43_pooling.py +41 -13
  237. package/md_cg/test_p44_md_whitebox.py +14 -1
  238. package/md_cg/test_protocol.py +243 -0
  239. package/md_cg/test_reach.py +378 -0
  240. package/md_cg/test_reach_keys.py +201 -0
  241. package/md_cg/test_reach_meta_exits.py +145 -0
  242. package/md_cg/test_read_clip.py +141 -0
  243. package/md_cg/test_retr_s1.py +340 -0
  244. package/md_cg/test_retr_s1b.py +209 -0
  245. package/md_cg/test_retr_s3.py +194 -0
  246. package/md_cg/test_retr_s4.py +163 -0
  247. package/md_cg/test_retr_s5.py +200 -0
  248. package/md_cg/test_retr_s6.py +157 -0
  249. package/md_cg/test_retr_s7.py +385 -0
  250. package/md_cg/test_retr_s8_time.py +316 -0
  251. package/md_cg/test_retr_s9_edges.py +286 -0
  252. package/md_cg/test_retr_s9_entity_ctx.py +175 -0
  253. package/md_cg/test_review_conformance.py +367 -0
  254. package/md_cg/test_role_views.py +354 -0
  255. package/md_cg/test_tasks.py +409 -0
  256. package/md_cg/test_tool_face.py +189 -0
  257. package/md_cg/test_trust.py +361 -0
  258. package/md_cg/test_twophase.py +286 -0
  259. package/md_cg/test_units_poll.py +71 -0
  260. package/md_cg/test_v14_fixes.py +397 -0
  261. package/md_cg/test_validity_filter.py +280 -0
  262. package/md_cg/test_wisdom_md_store.py +7 -3
  263. package/md_cg/test_writepipe.py +214 -0
  264. package/md_cg/theory.py +17 -2
  265. package/md_cg/tokens.py +134 -12
  266. package/md_cg/tool_face.py +261 -0
  267. package/md_cg/trust.py +943 -0
  268. package/md_cg/twophase.py +232 -0
  269. package/md_cg/units.py +665 -0
  270. package/md_cg/vision_evidence.py +25 -2
  271. package/md_cg/weights.py +25 -2
  272. package/md_cg/whitebox.py +33 -2
  273. package/md_cg/whitebox_kb/data/verify_cache.json +21233 -360
  274. package/md_cg/whitebox_kb/data/verify_savings.jsonl +5124 -0
  275. package/md_cg/whitebox_kb/wisdom/audit_log/chain_heat.json +10 -10
  276. package/md_cg/whitebox_kb/wisdom/code_compose.py +113 -6
  277. package/md_cg/whitebox_kb/wisdom/code_solidified.json +1 -1
  278. package/md_cg/whitebox_kb/wisdom/verifier.py +340 -55
  279. package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db +0 -0
  280. package/md_cg/writelimit.py +37 -7
  281. package/md_cg/writepipe.py +543 -0
  282. package/package.json +2 -2
  283. package/skills/skills/designer-perspective/scripts/__pycache__/designer.cpython-310.pyc +0 -0
  284. package/skills/skills/designer-perspective/scripts/designer.py +17 -1
  285. package/skills/skills/designer-perspective/tests/selftest.py +3 -1
  286. package/src/hooks.ts +27 -22
  287. package/src/index.ts +33 -6
  288. package/src/lib/datapath.ts +211 -13
  289. package/src/lib/mdcg_client.ts +12 -8
  290. package/src/lib/mutual.ts +411 -411
  291. package/src/lib/prompt_safety.ts +62 -0
  292. package/src/lib/token_store.ts +4 -5
  293. package/zcode/AGENTS.md +21 -10
  294. package/zcode/README.md +4 -0
  295. package/docs//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +0 -221
  296. /package/docs/{AGI → eval/AGI}/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v1.0.md" +0 -0
  297. /package/docs/{AGI → eval/AGI}/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v2.0.md" +0 -0
  298. /package/docs/{ → eval/}/345/256/236/351/252/214/346/226/271/346/241/210_/345/255/246/344/271/240/351/227/255/347/216/257AB/344/270/216/346/250/252/350/257/204_v1.0.md" +0 -0
  299. /package/docs/{ → eval/}/346/250/252/350/257/204_/345/205/255/345/256/266100/351/242/230/344/270/255/350/213/261/345/217/214/346/237/245_v1.0.md" +0 -0
  300. /package/docs/{guardrail-charter.md → mdcg/guardrail-charter.md} +0 -0
  301. /package/docs/{lingshu_tutorial.html → mdcg/lingshu_tutorial.html} +0 -0
  302. /package/docs/{memory-assessment.html → mdcg/memory-assessment.html} +0 -0
  303. /package/docs/{memory_score.html → mdcg/memory_score.html} +0 -0
  304. /package/docs/{memory_score.png → mdcg/memory_score.png} +0 -0
  305. /package/docs/{release_v0.3.0.md → mdcg/release_v0.3.0.md} +0 -0
  306. /package/docs/{release_v0.4.5.md → mdcg/release_v0.4.5.md} +0 -0
  307. /package/docs/{tool_table_v0.3.0.md → mdcg/tool_table_v0.3.0.md} +0 -0
  308. /package/docs/{ → mdcg/}/344/273/244/347/211/214/344/270/216/350/247/222/350/211/262/346/235/203/350/201/214/345/210/206/347/246/273_v0.1.md" +0 -0
  309. /package/docs/{ → mdcg/}/347/201/265/346/236/24282/345/267/245/345/205/267_/345/212/237/350/203/275/346/225/264/347/220/206/344/270/216/350/277/201/347/247/273/346/230/240/345/260/204_v0.1.md" +0 -0
  310. /package/docs/{ → mdcg/}/347/201/265/346/236/242MCP/345/267/245/345/205/267/346/200/273/350/241/250_v3.4.md" +0 -0
  311. /package/docs/{ → mdcg/}/347/201/265/346/236/242_/350/207/252/346/210/221/345/261/202/345/256/232/344/271/211.md" +0 -0
  312. /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_MD/347/233/256/345/275/225/346/226/271/346/241/210_v0.1.md" +0 -0
  313. /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_/346/235/241/344/273/266/347/251/272/351/227/264/345/220/210/346/210/220/344/270/216/347/224/237/346/225/210/346/235/241/344/273/266/345/217/243/345/276/204_v0.1.md" +0 -0
  314. /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276/344/275/234/344/270/272/350/256/260/345/277/206/346/223/215/344/275/234/347/263/273/347/273/237_/350/257/204/344/274/260/344/270/216/350/267/257/347/272/277_v0.1.md" +0 -0
  315. /package/docs/{ → plans/}/345/221/275/345/220/215/346/262/273/347/220/206_/351/241/271/347/233/256/350/256/241/345/210/222.md" +0 -0
  316. /package/docs/{ → plans/}/347/237/245/350/257/206/345/233/276/350/260/261/351/241/271/347/233/256_/344/272/244/346/216/245/346/226/207/346/241/243_20260914.md" +0 -0
  317. /package/docs/{ → swarm/}/350/234/202/347/276/244/345/244/232/346/231/272/350/203/275/344/275/223_/344/275/277/347/224/250/346/212/245/345/221/212_20260913.md" +0 -0
  318. /package/docs/{ → swarm/}/350/234/202/347/276/244/345/244/232/346/231/272/350/203/275/344/275/223_/345/212/237/350/203/275/350/257/264/346/230/216_v0.6.md" +0 -0
  319. /package/docs/{ → swarm/}/350/234/202/347/276/244/347/233/262/345/214/272/346/240/207/350/256/260_v1.0.md" +0 -0
  320. /package/docs/{ → theory/}/344/277/241/346/201/257/345/267/256/344/270/272/344/273/200/344/271/210/345/277/205/347/204/266/345/255/230/345/234/250/344/270/224/350/207/252/347/204/266/346/211/251/345/244/247.md" +0 -0
  321. /package/docs/{ → theory/}/346/231/272/350/203/275/347/232/204/345/205/254/347/220/206/345/214/226/345/237/272/347/237/263.md" +0 -0
  322. /package/docs/{ → theory/}/346/231/272/350/203/275/347/232/204/350/256/244/347/237/245/350/277/207/347/250/213.md" +0 -0
  323. /package/docs/{ → theory/}/346/231/272/350/203/275/350/256/2723.4.md" +0 -0
  324. /package/docs/{ → theory/}/347/231/275/347/256/261/346/231/272/350/203/275/346/230/257/344/273/200/344/271/210/357/274/237.md" +0 -0
  325. /package/docs/{ → theory/}/347/231/275/347/256/261/346/231/272/350/203/275/347/263/273/345/210/227/302/267/347/254/254/344/272/224/347/257/207/357/274/232/350/256/251AI/347/234/237/346/255/243/350/243/205/344/270/212/350/256/260/345/277/206.md" +0 -0
@@ -0,0 +1,361 @@
1
+ """可验证记忆单元(trust)验收:状态机 / 依赖图 / 双时间轴 / 失效传播 / 闸门 / 输出协议。
2
+
3
+ 运行:python -m md_cg.test_trust (或 python md_cg/test_trust.py)
4
+ 范式:无第三方依赖的直接运行脚本(assert + 计数 + 非零退出),对齐 md_cg/test_*.py。
5
+
6
+ 覆盖面(每个断言都在验「语义」而非「实现细节」):
7
+ ① 纯函数裁决:can_transition / check / require_transition
8
+ ② as_deps 解析与 parse_time 容错
9
+ ③ validity 双时间轴四态
10
+ ④ state_of 缺省语义 + stamp 幂等
11
+ ⑤ set_state 端到端(负路由 / 幂等 / 审计留痕 / 保护节点)
12
+ ⑥ cg.add 覆写继承(全量重建 fm 不丢验证态与依赖)
13
+ ⑦ 依赖反查索引 + 缓存失效
14
+ ⑧ 一跳同步传播 mark_dependents(预演 / 落盘 / 幂等)
15
+ ⑨ 多跳异步 propagate(BFS 可达 / 默认预演)
16
+ ⑩ 写入闸门 E050 / E051 / 哨兵与自述豁免(真源 nodefile.declares_dependency;
17
+ 收窄裁定 b:仅 `@<节点 id>` 显式引用算依赖声明,自然语言自述不算)
18
+ ⑪ 输出协议 statushdr(四态符号 / 开关)
19
+ ⑫ 巡检 patrol 只读 + catalog 自描述
20
+ """
21
+ import json
22
+ import os
23
+ import shutil
24
+ import sys
25
+ import tempfile
26
+
27
+ try:
28
+ from . import trust, statushdr, nodefile, writepipe
29
+ from .mdcg import MdCG
30
+ except ImportError: # 直接脚本运行
31
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
32
+ from md_cg import trust, statushdr, nodefile, writepipe
33
+ from md_cg.mdcg import MdCG
34
+
35
+ PASS = 0
36
+ FAIL = []
37
+
38
+
39
+ def ok(cond, label):
40
+ global PASS
41
+ if cond:
42
+ PASS += 1
43
+ else:
44
+ FAIL.append(label)
45
+ print(" FAIL: " + label)
46
+
47
+
48
+ def eq(got, want, label):
49
+ ok(got == want, "%s(got=%r want=%r)" % (label, got, want))
50
+
51
+
52
+ def phase(name):
53
+ print("--- " + name)
54
+
55
+
56
+ A, B, C, D = ("mem_1789810000001", "mem_1789810000002",
57
+ "mem_1789810000003", "mem_1789810000004")
58
+
59
+
60
+ def ccg(node, deps=None, **kw):
61
+ """构造一条 CCG 契约正文(六要素齐;子功能按 deps 给出,**显式 @ 引用形态**)。
62
+
63
+ 注意:跨节点依赖的声明形态是 `@<节点 id>`(收窄裁定 b,2026-09-19)——
64
+ 自然语言自述子功能不构成依赖声明,故此处必须带 `@`,否则本文件的闸门断言
65
+ 测的就不是依赖链路了。
66
+ """
67
+ sub = ("依赖 " + "、".join("@" + d for d in deps)) if deps else "无"
68
+ return ("# 功能名:%s\n# 生效条件:无条件\n# 子功能:%s\n# 执行:无\n"
69
+ "# 验证方式:test\n# 不适用条件:无\n\n正文。" % (node, sub))
70
+
71
+
72
+ def main():
73
+ tmp = tempfile.mkdtemp(prefix="mdcg_test_trust_")
74
+ cg = MdCG(root=os.path.join(tmp, "kg"))
75
+
76
+ # ---------------------------------------------------------------- ① 纯函数裁决
77
+ phase("① 裁决")
78
+ ok(trust.can_transition("unverified", "verified"), "unverified→verified 合法")
79
+ ok(trust.can_transition("verified", "doubted"), "verified→doubted 合法")
80
+ ok(trust.can_transition("unverified", "doubted"), "unverified→doubted 合法(负证据)")
81
+ ok(not trust.can_transition("verified", "unverified"), "verified→unverified 拒绝(回落)")
82
+ ok(not trust.can_transition("doubted", "expired"), "doubted→expired 拒绝(跳级)")
83
+ ok(not trust.can_transition("unverified", "nope"), "未知状态拒绝")
84
+ eq(trust.check("verified", "verified")[1], "noop", "同状态查得 noop")
85
+ eq(trust.check("verified", "unverified")[1], "illegal_transition", "非法迁移码")
86
+ ok(trust.check("verified", "unverified", protected=True, override=False)[0] is False,
87
+ "受保护节点拒绝降级")
88
+ ok(trust.check("unverified", "verified", protected=True, override=False)[0] is True,
89
+ "受保护节点允许升级")
90
+ ok(trust.check("verified", "doubted", protected=True, override=True)[0] is True,
91
+ "override 放行合法降级(verified→doubted)")
92
+ ok(trust.check("verified", "unverified", protected=True, override=True)[0] is False,
93
+ "override 不豁免非法迁移(合法性优先于保护)")
94
+ raised = False
95
+ try:
96
+ trust.require_transition("verified", "unverified")
97
+ except trust.TrustError:
98
+ raised = True
99
+ ok(raised, "require_transition 非法即抛 TrustError")
100
+ ok(trust.is_downgrade("verified", "doubted"), "verified→doubted 是降级")
101
+
102
+ # ---------------------------------------------------------------- ② 解析容错
103
+ phase("② 解析")
104
+ eq(trust.as_deps(["b", "a", "b"]), ["a", "b"], "as_deps 列表去重排序")
105
+ eq(trust.as_deps("x"), ["x"], "as_deps 单串")
106
+ eq(trust.as_deps(None), [], "as_deps None")
107
+ eq(trust.as_deps([None, ""]), [], "as_deps 过滤空值")
108
+ eq(trust.as_deps("a, b"), ["a", "b"], "as_deps 逗号串")
109
+ ok(trust.parse_time("2020-01-01") is not None, "parse_time 日期串")
110
+ ok(trust.parse_time(None) is None, "parse_time None")
111
+ ok(trust.parse_time(1700000000.0) == 1700000000.0, "parse_time 数字直通")
112
+ ok(trust.parse_time("不是时间") is None, "parse_time 非法容错")
113
+
114
+ # ---------------------------------------------------------------- ③ 双时间轴
115
+ phase("③ 时间轴")
116
+ eq(trust.validity({"valid_from": "2020-01-01", "valid_until": "2030-01-01"})[0],
117
+ "active", "区间内=active")
118
+ eq(trust.validity({"valid_from": "2099-01-01"})[0], "not_yet", "未来=not_yet")
119
+ eq(trust.validity({"valid_until": "2001-01-01"})[0], "expired", "过去=expired")
120
+ eq(trust.validity({})[0], "unknown", "无时间轴=unknown(不受限,非 active)")
121
+ eq(trust.validity(None)[0], "unknown", "非 dict 亦 unknown(不猜测)")
122
+ # 2026-09-19 阶段一:规范键优先 + 别名回落 + 信念时间(第三类语义)物理隔离
123
+ eq(trust.validity({"effective_from": "2099-01-01"})[0], "not_yet",
124
+ "规范键起点生效")
125
+ eq(trust.validity({"effective_from": "2099-01-01",
126
+ "valid_from": "2020-01-01"})[0], "not_yet",
127
+ "规范键优先于历史键")
128
+ eq(trust.validity({"effective_until": "坏值",
129
+ "valid_until": "2001-01-01"})[0], "expired",
130
+ "规范键不可解析 → 继续回落别名(不猜测定案)")
131
+ eq(trust.validity({"effective_until": "2001-01-01"})[0], "expired",
132
+ "规范键终点生效")
133
+ eq(trust.validity({"believed_at": "2001-01-01"})[0], "unknown",
134
+ "信念时间不参与时效判定(第三类语义)")
135
+ ok(trust.believed_at({"believed_at": "2020-01-01"}) is not None
136
+ and trust.believed_at({}) is None, "believed_at 读取器(epoch/None)")
137
+
138
+ # ---------------------------------------------------------------- ④ 缺省与幂等
139
+ phase("④ 缺省/幂等")
140
+ eq(trust.state_of({}), "unverified", "缺字段按 unverified")
141
+ eq(trust.state_of(None), "unverified", "None 按 unverified")
142
+ fm = {}
143
+ r1 = trust.stamp(fm, "verified", reason="t", actor="t")
144
+ eq(r1[1], "ok", "stamp 首次迁移 code=ok")
145
+ eq(trust.state_of(fm), "verified", "stamp 落字段")
146
+ before = json.dumps(fm, sort_keys=True, default=str)
147
+ r2 = trust.stamp(fm, "verified", reason="t", actor="t")
148
+ eq(json.dumps(fm, sort_keys=True, default=str), before, "同状态 stamp 不重复写")
149
+ eq(r2[1], "noop", "同状态 stamp code=noop(幂等不写盘)")
150
+
151
+ # ---------------------------------------------------------------- ⑤ set_state 端到端
152
+ phase("⑤ set_state")
153
+ missing = trust.set_state(cg, "mem_not_exist", "verified")
154
+ ok(missing.get("ok") is False and missing.get("error") == "node_not_found",
155
+ "不存在节点负路由 node_not_found")
156
+ cg.add(A, ccg("上游"), layer="knowledge", tags=["t1"])
157
+ r = cg.set_verification(A, "verified", reason="首次", actor="tester",
158
+ evidence="ev-1", method="test")
159
+ ok(r.get("ok") and r.get("changed"), "set_verification verified 成功")
160
+ eq(trust.state_of((cg.get(A) or {}).get("frontmatter")), "verified",
161
+ "fm 落 verification_state")
162
+ eq((cg.index["nodes"][A] or {}).get("verification_state"), "verified",
163
+ "索引快照同步(免读文件)")
164
+ r2 = cg.set_verification(A, "verified", reason="重复", actor="tester")
165
+ ok(r2.get("ok") and r2.get("changed") is False, "幂等:同状态 changed=False")
166
+ led = trust.load_ledger(cg, node_id=A)
167
+ ok(len(led) >= 1, "审计台账留痕")
168
+ ok(all("to" in x for x in led), "台账含 to 字段")
169
+ bad = cg.set_verification(A, "unverified", reason="回落", actor="tester")
170
+ ok(bad.get("ok") is False, "非法迁移负路由不抛")
171
+
172
+ # ---------------------------------------------------------------- ⑥ 覆写继承
173
+ phase("⑥ 覆写继承")
174
+ cg.add(B, ccg("下游", [A]), layer="knowledge", depends_on=[A],
175
+ valid_from="2020-01-01", valid_until="2030-01-01")
176
+ cg.set_verification(B, "verified", reason="t", actor="t")
177
+ cg.add(B, ccg("下游 v2", [A]), layer="knowledge", depends_on=[A])
178
+ fmb = (cg.get(B) or {}).get("frontmatter") or {}
179
+ eq(trust.state_of(fmb), "verified", "覆写后验证态继承(防静默打回)")
180
+ eq(fmb.get("depends_on"), [A], "覆写后依赖继承")
181
+ eq(fmb.get("valid_from"), "2020-01-01", "覆写后时间轴继承")
182
+ # 阶段一:规范键与信念时间的覆写继承(同 lifecycle/verification 的坑)
183
+ cg.add(B, ccg("下游 v3", [A]), layer="knowledge", depends_on=[A],
184
+ effective_from="2021-01-01", effective_until="2031-01-01",
185
+ believed_at="2022-01-01")
186
+ fmb3 = (cg.get(B) or {}).get("frontmatter") or {}
187
+ eq(fmb3.get("effective_from"), "2021-01-01", "新规范参数落规范键")
188
+ eq("valid_from" in fmb3, False, "落规范键时剔同族历史键(禁同族双写歧义)")
189
+ eq(fmb3.get("believed_at"), "2022-01-01", "信念时间写入落 believed_at")
190
+ cg.add(B, ccg("下游 v4", [A]), layer="knowledge", depends_on=[A])
191
+ fmb4 = (cg.get(B) or {}).get("frontmatter") or {}
192
+ eq(fmb4.get("effective_from"), "2021-01-01", "覆写后规范起点继承")
193
+ eq(fmb4.get("effective_until"), "2031-01-01", "覆写后规范终点继承")
194
+ eq(fmb4.get("believed_at"), "2022-01-01", "覆写后信念时间继承")
195
+
196
+ # ---------------------------------------------------------------- ⑦ 反查索引
197
+ phase("⑦ 反查索引")
198
+ idx = trust.dependents_index(cg)
199
+ ok(B in idx.get(A, []), "反查:谁依赖 A")
200
+ eq(trust.deps_of(cg, B), [A], "正查:B 依赖谁")
201
+ cg.add(D, ccg("新下游", [A]), layer="knowledge", depends_on=[A])
202
+ trust.invalidate_cache(cg)
203
+ ok(D in trust.dependents_index(cg).get(A, []), "写后缓存失效可重建")
204
+
205
+ # ---------------------------------------------------------------- ⑧ 一跳传播
206
+ phase("⑧ 一跳同步传播")
207
+ dry = trust.mark_dependents(cg, A, reason="预演", apply=False)
208
+ ok(dry.get("dry_run") and len(dry.get("dependents") or []) >= 2,
209
+ "预演列出全部下游且不改盘")
210
+ eq(dry.get("dependents"), sorted(dry.get("dependents") or []), "下游有序输出")
211
+ ok(trust.state_of((cg.get(B) or {}).get("frontmatter")) == "verified",
212
+ "预演未改盘(B 仍 verified)")
213
+ wet = trust.mark_dependents(cg, A, reason="上游变动", actor="tester", trigger="t")
214
+ ok(wet.get("changed") >= 2, "落盘:下游全部标存疑")
215
+ eq(trust.state_of((cg.get(B) or {}).get("frontmatter")), "doubted", "B 已 doubted")
216
+ eq(trust.state_of((cg.get(D) or {}).get("frontmatter")), "doubted", "D 已 doubted")
217
+ again = trust.mark_dependents(cg, A, reason="重复", actor="tester")
218
+ eq(again.get("changed"), 0, "重复传播幂等(changed=0)")
219
+ none_dep = trust.mark_dependents(cg, C, reason="无下游")
220
+ eq(none_dep.get("changed"), 0, "无下游不报错")
221
+
222
+ # ---------------------------------------------------------------- ⑨ 多跳传播
223
+ phase("⑨ 多跳异步传播")
224
+ cg.add(C, ccg("中继", [D]), layer="knowledge", depends_on=[D])
225
+ cg.set_verification(D, "verified", reason="t", actor="t")
226
+ cg.set_verification(C, "verified", reason="t", actor="t")
227
+ cg.set_verification(A, "expired", reason="时间到", actor="tester")
228
+ dry2 = trust.propagate(cg)
229
+ ok(dry2.get("dry_run"), "propagate 默认预演")
230
+ ok(dry2.get("reachable", 0) >= 2, "预演可达多跳下游")
231
+ wet2 = trust.propagate(cg, apply=True, actor="tester")
232
+ ok(wet2.get("dry_run") is False, "apply=True 落盘")
233
+ eq(trust.state_of((cg.get(B) or {}).get("frontmatter")), "doubted",
234
+ "一跳下游 doubted")
235
+ eq(trust.state_of((cg.get(C) or {}).get("frontmatter")), "doubted",
236
+ "二跳下游 doubted(BFS 可达)")
237
+
238
+ # ---------------------------------------------------------------- ⑩ 写入闸门
239
+ phase("⑩ 写入闸门")
240
+ ok(nodefile.declares_dependency(ccg("x", [A])), "显式 @ 引用判为 True")
241
+ ok(not nodefile.declares_dependency(ccg("x")), "「无」哨兵判为 False")
242
+ ok(not nodefile.declares_dependency("# 功能名:x\n\n正文"), "无该行判为 False")
243
+ ok(not nodefile.declares_dependency("# 子功能:无(不依赖其他单元)"),
244
+ "哨兵带括号说明仍为 False")
245
+ # 收窄裁定 b(2026-09-19):区分「自述子功能」与「跨节点依赖」——只有显式
246
+ # `@<节点 id>` 引用才构成依赖声明;自然语言自述不算。否则 CCG 编译产物(六要素
247
+ # 必含「子功能」行)落库后会被 E050 永久锁死(test_ccgc V16f 即该死锁的实证)。
248
+ ok(not nodefile.is_dep_sentinel("无法确定依赖范围"),
249
+ "「无法确定」不得误判为哨兵")
250
+ ok(not nodefile.declares_dependency("# 子功能:无法确定依赖范围"),
251
+ "自然语言自述不算依赖声明(收窄 b)")
252
+ ok(not nodefile.declares_dependency(
253
+ "# 子功能:按扩展名把文件路由到对应摄取器"),
254
+ "CCG 编译产物形态的自述子功能不构成依赖声明(V16f 死锁根因)")
255
+ ok(not nodefile.declares_dependency("# 子功能:联系 user@example.com"),
256
+ "邮箱形态的 @ 不误判为引用(@ 左侧为标识符字符)")
257
+ ok(not nodefile.declares_dependency("# 子功能:@所有人 知悉"),
258
+ "中文 @ 不误判为引用(id 须以 [A-Za-z0-9_] 起头)")
259
+ eq(nodefile.dep_refs("依赖 @mem_a、@code_b 与 @mem_a(回指 @mem_a)"),
260
+ ["mem_a", "code_b"], "多引用去重保序提取")
261
+ eq(nodefile.dep_refs("@code_3552109b6163"), ["code_3552109b6163"],
262
+ "行首 @ 命中(左侧非标识符字符即可)")
263
+ eq(nodefile.dep_refs("无"), [], "哨兵值提取为空列表")
264
+ g1 = writepipe._gate_deps({"cg": cg, "nid": "mem_x1",
265
+ "a": {"content": ccg("x", [A]),
266
+ "depends_on": [A]}, "verdict": {}})
267
+ ok(g1 is None, "声明且目标可解析→放行")
268
+ g2 = writepipe._gate_deps({"cg": cg, "nid": "mem_x2",
269
+ "a": {"content": ccg("x", [A]),
270
+ "depends_on": A}, "verdict": {}})
271
+ ok(g2 is None, "单值形态同样放行(as_deps 容错)")
272
+ g2b = writepipe._gate_deps({"cg": cg, "nid": "mem_x2b",
273
+ "a": {"content": ccg("x", [A]),
274
+ "depends_on": "%s, mem_ghost2" % A},
275
+ "verdict": {}})
276
+ eq((g2b or {}).get("error"), "E051", "逗号串逐项解析(含悬空即拒)")
277
+ g3 = writepipe._gate_deps({"cg": cg, "nid": "mem_x3",
278
+ "a": {"content": "# 功能名:x\n# 子功能:依赖 @mem_ghost\n"},
279
+ "verdict": {}})
280
+ eq((g3 or {}).get("error"), "E050", "显式 @ 声明却无字段→E050 硬拒")
281
+ ok((g3 or {}).get("committed") is False, "硬拒体 committed=False")
282
+ g3b = writepipe._gate_deps({"cg": cg, "nid": "mem_x3b",
283
+ "a": {"content": "# 功能名:x\n# 子功能:依赖某物\n"},
284
+ "verdict": {}})
285
+ ok(g3b is None, "自然语言自述子功能不触发闸门(收窄 b 的行为面)")
286
+ g4 = writepipe._gate_deps({"cg": cg, "nid": "mem_x4",
287
+ "a": {"content": ccg("x", ["mem_ghost"]),
288
+ "depends_on": ["mem_ghost"]}, "verdict": {}})
289
+ eq((g4 or {}).get("error"), "E051", "依赖悬空→E051 硬拒")
290
+ eq((g4 or {}).get("missing"), ["mem_ghost"], "E051 回带缺失目标")
291
+ g5 = writepipe._gate_deps({"cg": cg, "nid": "mem_x5",
292
+ "a": {"content": ccg("x")}, "verdict": {}})
293
+ ok(g5 is None, "哨兵值不触发闸门")
294
+ names = writepipe.default_pipeline().names()
295
+ ok("deps" in str(names) and "trust" in str(names), "闸与观察者已注册默认链")
296
+
297
+ # ---------------------------------------------------------------- ⑪ 输出协议
298
+ phase("⑪ 输出协议")
299
+ head_b = statushdr.render(cg, B)
300
+ ok(bool(head_b), "doubted 节点有状态头")
301
+ ok(statushdr.MARK_DOUBTED in head_b, "状态头含存疑符号")
302
+ head_a = statushdr.render(cg, A)
303
+ ok(statushdr.MARK_ABNORMAL in head_a or statushdr.MARK_EXPIRED in head_a
304
+ or bool(head_a), "异常/过期节点有状态头")
305
+ ok(isinstance(statushdr.attach_heads(cg, {"node_id": B}), dict), "attach_heads 返回 dict")
306
+ ok(statushdr.enabled(), "状态头默认开启")
307
+
308
+ # ---------------------------------------------------------------- ⑫ 巡检与自描述
309
+ phase("⑫ 巡检/自描述")
310
+ rep = trust.patrol(cg)
311
+ ok(rep.get("readonly") is True, "巡检只读标记")
312
+ ok(rep.get("nodes", 0) >= 4, "巡检统计节点数")
313
+ ok(rep.get("doubted_count", 0) >= 2, "巡检统计存疑积压")
314
+ snap = trust.state_of((cg.get(B) or {}).get("frontmatter"))
315
+ trust.patrol(cg)
316
+ eq(trust.state_of((cg.get(B) or {}).get("frontmatter")), snap, "巡检不改状态")
317
+ cat = trust.catalog(cg.root)
318
+ for key in ("layer", "question", "states", "state_field", "deps_field",
319
+ "time_fields", "ledger", "discipline"):
320
+ ok(key in cat, "catalog 含键 " + key)
321
+ ok("unverified" in cat["states"] and "doubted" in cat["states"], "catalog 状态枚举")
322
+ s = trust.summary(cg)
323
+ ok(isinstance(s, dict) and "doubted" in s, "summary 轻量摘要")
324
+ de = trust.describe(cg, B)
325
+ ok(de.get("ok") and de.get("depends_on") == [A], "describe 单节点全貌")
326
+ ok(de.get("depended_by") is not None, "describe 含反查")
327
+
328
+ # ---------------------------------------------------------------- ⑬ MCP 面接线
329
+ phase("⑬ MCP 面:status op / 状态头 / 写闸硬拒")
330
+ from md_cg import mcp_server as MS
331
+ st = MS._cg_call(cg, {"op": "status", "node_id": A})
332
+ eq(st.get("ok"), True, "op=status 单节点返回 ok")
333
+ eq(st.get("state") or st.get("verification_state"), "expired", "status 透出验证态")
334
+ ok(bool(st.get("status_head")), "status 带状态头")
335
+ ok(isinstance(st.get("ledger"), list), "status 带履历台账")
336
+ ok("depended_by" in st or "dependents" in st, "status 带下游反查")
337
+ sm = MS._cg_call(cg, {"op": "status"})
338
+ ok(isinstance(sm.get("summary"), dict), "op=status 摘要形态返回 summary")
339
+ ok("status_head" in sm or "status_heads" in sm, "摘要形态亦挂状态头")
340
+ sl = MS._cg_call(cg, {"op": "status", "action": "ledger"})
341
+ ok(isinstance(sl.get("rows"), list), "action=ledger 读台账")
342
+
343
+ # 写闸硬拒(MCP 端到端):正文声明依赖却无可解析目标 → E050 / 悬空 → E051
344
+ r50 = MS._cg_call(cg, {"op": "write", "content": ccg("闸门缺声明", [A]),
345
+ "content_kind": "text", "layer": "knowledge"})
346
+ eq((r50 or {}).get("error"), "E050", "写闸硬拒 E050(声明依赖却无 depends_on)")
347
+ r51 = MS._cg_call(cg, {"op": "write", "content": ccg("闸门悬空", ["mem_ghost_zz"]),
348
+ "content_kind": "text", "layer": "knowledge",
349
+ "depends_on": ["mem_ghost_zz"]})
350
+ eq((r51 or {}).get("error"), "E051", "写闸硬拒 E051(依赖目标悬空)")
351
+ ok(not (cg.get("mem_ghost_zz") or {}), "悬空目标未落盘(闸门真拒非事后回滚)")
352
+
353
+ shutil.rmtree(tmp, ignore_errors=True)
354
+ print("\nPASS=%d FAIL=%d" % (PASS, len(FAIL)))
355
+ if FAIL:
356
+ for f in FAIL:
357
+ print(" - " + f)
358
+ sys.exit(1)
359
+
360
+
361
+ main()
@@ -0,0 +1,286 @@
1
+ # -*- coding: utf-8 -*-
2
+ """test_twophase · 写入两段式提交(Pi 可学习优点 ③)
3
+
4
+ 验收口径(交接文档 §3③):
5
+ - 写入在**闸门通过后先落 intent**(含内容指纹),落盘完成再落 outcome;
6
+ - 「kill -9 模拟半途写入后重启」→ 对账**正确补账**(内容确已落盘)或
7
+ **如实标记**(未落盘 / 指纹不符),不假装成功;
8
+ - 对账幂等、账本 append-only、不重放写入;
9
+ - 未落盘的分支(闸门拒绝 / 入队)不产生 intent——没打算写就不记账。
10
+
11
+ 运行:python -m md_cg.test_twophase
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import os
17
+ import shutil
18
+ import tempfile
19
+ import traceback
20
+
21
+ from . import twophase, writepipe
22
+ from .mdcos import MdCGSecure
23
+ from .security import Principal
24
+
25
+ _ok = 0
26
+ _fail = []
27
+ _seq = [0]
28
+
29
+
30
+ def check(name, cond, detail=""):
31
+ global _ok
32
+ if cond:
33
+ _ok += 1
34
+ print("[ok] " + name)
35
+ else:
36
+ _fail.append(name)
37
+ print("[FAIL] %s · %s" % (name, str(detail)[:240]))
38
+
39
+
40
+ def _mk_cg(tmp, role="designer"):
41
+ _seq[0] += 1
42
+ root = os.path.join(tmp, "root_%02d_%s" % (_seq[0], role))
43
+ p = Principal(tenant="default", actor="t_" + role, role=role,
44
+ can_write=(role != "guest"), can_admin=(role == "designer"))
45
+ return MdCGSecure(root, principal=p)
46
+
47
+
48
+ def _policy(tmp, required=("PASSED",), forbidden=("FORBIDDEN_WORD",)):
49
+ path = os.path.join(tmp, "policy.json")
50
+ with open(path, "w", encoding="utf-8") as f:
51
+ json.dump({"forbidden": list(forbidden),
52
+ "required": list(required)}, f)
53
+ os.environ["MDCG_POLICY_FILE"] = path
54
+ return path
55
+
56
+
57
+ def _lines(cg):
58
+ p = twophase.log_path(cg)
59
+ if not os.path.isfile(p):
60
+ return []
61
+ with open(p, encoding="utf-8", errors="replace") as f:
62
+ return [x for x in f.read().splitlines() if x.strip()]
63
+
64
+
65
+ def _recs(cg):
66
+ return twophase.records(cg)
67
+
68
+
69
+ def _idx(cg, nid):
70
+ return (cg.index.get("nodes") or {}).get(nid) or {}
71
+
72
+
73
+ def _phases(cg, nid=None):
74
+ return [r.get("phase") for r in _recs(cg)
75
+ if nid is None or r.get("id") == nid]
76
+
77
+
78
+ def _run(tmp):
79
+ _policy(tmp)
80
+ pipe = writepipe.install_default_gates(writepipe.WritePipeline())
81
+
82
+ # ---------- ① 正常落盘:intent + outcome 成对 ----------
83
+ cg = _mk_cg(tmp)
84
+ out = pipe.execute(cg, {"content_kind": "text",
85
+ "content": "两段式正常落盘 PASSED",
86
+ "layer": "knowledge"})
87
+ nid = out.get("id")
88
+ check("①1 写入成功(committed)", out.get("committed") is True, out)
89
+ recs = _recs(cg)
90
+ ph = [r["phase"] for r in recs if r.get("id") == nid]
91
+ check("①2 账本两行成对(intent → outcome)", ph == ["intent", "outcome"], ph)
92
+ intent = [r for r in recs if r.get("id") == nid
93
+ and r["phase"] == "intent"][0]
94
+ oc = [r for r in recs if r.get("id") == nid and r["phase"] == "outcome"][0]
95
+ check("①3 intent 带内容指纹(对账的判据)",
96
+ bool(intent.get("content_hash")), intent)
97
+ check("①4 outcome 记 committed + 同 iid",
98
+ oc.get("status") == twophase.STATUS_COMMITTED
99
+ and oc.get("iid") == intent.get("iid"), oc)
100
+ check("①5 指纹走 nodefile 唯一真源(含写盘尾换行归一)",
101
+ intent.get("content_hash")
102
+ == twophase.nodefile.content_hash("两段式正常落盘 PASSED"),
103
+ (intent.get("content_hash"),
104
+ twophase.nodefile.content_hash("两段式正常落盘 PASSED")))
105
+ check("①6 正常写入后无未结清意图", cg.pending_writes() == [])
106
+
107
+ # ---------- ② intent 真的**先于**落盘(真实执行路径内观测) ----------
108
+ cg2 = _mk_cg(tmp)
109
+ _orig = writepipe._executor
110
+ seen = {}
111
+
112
+ def _probe(ctx):
113
+ seen["has_intent"] = any(
114
+ r.get("phase") == "intent" and r.get("id") == ctx["nid"]
115
+ for r in _recs(ctx["cg"]))
116
+ seen["has_outcome"] = any(
117
+ r.get("phase") == "outcome" and r.get("id") == ctx["nid"]
118
+ for r in _recs(ctx["cg"]))
119
+ seen["node_exists"] = bool(ctx["cg"].get(ctx["nid"]))
120
+ return _orig(ctx)
121
+
122
+ writepipe._executor = _probe
123
+ try:
124
+ pipe.execute(cg2, {"content_kind": "text",
125
+ "content": "先行持久化探针 PASSED",
126
+ "layer": "knowledge"})
127
+ finally:
128
+ writepipe._executor = _orig
129
+ check("②1 进入执行器时 intent 已落账(先行持久化成立)",
130
+ seen.get("has_intent") is True, seen)
131
+ check("②2 进入执行器时**尚无** outcome 且节点未落盘",
132
+ seen.get("has_outcome") is False
133
+ and seen.get("node_exists") is False, seen)
134
+
135
+ # ---------- ③ 半途崩溃:落盘已完成、outcome 未写 ----------
136
+ cg3 = _mk_cg(tmp)
137
+ nid3 = "mem_2pc_c3"
138
+ tok = twophase.begin(cg3, nid3, "崩溃在落盘之后 PASSED", layer="knowledge",
139
+ actor="test:crash")
140
+ cg3.add(nid3, "崩溃在落盘之后 PASSED", layer="knowledge")
141
+ check("③1 崩溃现场:有 intent 无 outcome",
142
+ _phases(cg3, nid3) == ["intent"]
143
+ and len(cg3.pending_writes()) == 1)
144
+ rep = cg3.reconcile_writes()
145
+ check("③2 对账补账(内容指纹吻合 → committed)",
146
+ rep.get("unpaired") == 1 and rep.get("committed") == 1
147
+ and rep.get("interrupted") == 0, rep)
148
+ check("③3 补出的 outcome 标 reconciled + 判据可审计",
149
+ any(r.get("phase") == "outcome" and r.get("reconciled")
150
+ and r.get("reason") == twophase.R_NODE_OK
151
+ for r in _recs(cg3)), _recs(cg3)[-1])
152
+ check("③4 对账后 pending 归零",
153
+ cg3.pending_writes() == [])
154
+
155
+ # ---------- ④ 半途崩溃:根本没落盘 ----------
156
+ cg4 = _mk_cg(tmp)
157
+ twophase.begin(cg4, "mem_2pc_c4", "崩溃在落盘之前 PASSED",
158
+ layer="knowledge")
159
+ rep4 = cg4.reconcile_writes()
160
+ check("④1 未落盘 → 如实标记 interrupted(不假装成功)",
161
+ rep4.get("unpaired") == 1 and rep4.get("interrupted") == 1
162
+ and rep4.get("committed") == 0, rep4)
163
+ check("④2 interrupted 判据 = node_missing",
164
+ rep4["details"][0]["reason"] == twophase.R_NODE_MISSING, rep4)
165
+ check("④3 未落盘节点确实不在库(标记与实际一致)",
166
+ cg4.get("mem_2pc_c4") is None)
167
+
168
+ # ---------- ⑤ 指纹不符:落了别的正文 ----------
169
+ cg5 = _mk_cg(tmp)
170
+ twophase.begin(cg5, "mem_2pc_c5", "意图写的是这一段 PASSED")
171
+ cg5.add("mem_2pc_c5", "实际落的是另一段 PASSED", layer="knowledge")
172
+ rep5 = cg5.reconcile_writes()
173
+ check("⑤1 指纹不符 → interrupted(content_mismatch)",
174
+ rep5.get("interrupted") == 1
175
+ and rep5["details"][0]["reason"] == twophase.R_MISMATCH, rep5)
176
+ check("⑤2 对账**不覆盖**既有正文(只标记不重放)",
177
+ "实际落的是另一段" in (cg5.get("mem_2pc_c5") or {}).get("content", ""))
178
+
179
+ # ---------- ⑥ dry-run / 幂等 / append-only ----------
180
+ cg6 = _mk_cg(tmp)
181
+ twophase.begin(cg6, "mem_2pc_c6", "dry-run 用例 PASSED")
182
+ before = _lines(cg6)
183
+ dry = cg6.reconcile_writes(apply=False)
184
+ check("⑥1 dry-run 报告未结清但不改账本",
185
+ dry.get("unpaired") == 1 and dry.get("applied") is False
186
+ and _lines(cg6) == before, dry)
187
+ cg6.reconcile_writes()
188
+ n_after_first = len(_lines(cg6))
189
+ rep6b = cg6.reconcile_writes()
190
+ check("⑥2 对账幂等(二次无未结清、不追加行)",
191
+ rep6b.get("unpaired") == 0
192
+ and len(_lines(cg6)) == n_after_first, rep6b)
193
+ check("⑥3 账本 append-only(原记录逐字保留)",
194
+ _lines(cg6)[:len(before)] == before)
195
+ snap = _lines(cg6)
196
+ cg6.pending_writes()
197
+ check("⑥4 pending 只读(不改账本)", _lines(cg6) == snap)
198
+
199
+ # ---------- ⑦ 执行器抛异常 → outcome=error,异常照抛 ----------
200
+ cg7 = _mk_cg(tmp, role="guest") # guest 无写权限 → 库层拒绝
201
+ pipe7 = writepipe.WritePipeline() # 不装闸:直接打执行器
202
+ raised = None
203
+ try:
204
+ pipe7.execute(cg7, {"content": "无权限写入 PASSED",
205
+ "layer": "knowledge"})
206
+ except Exception as exc: # AccessDenied 或其子类
207
+ raised = type(exc).__name__
208
+ check("⑦1 库层权限拒绝照抛(两段式不改错误语义)", raised is not None,
209
+ raised)
210
+ check("⑦2 失败也留痕:outcome=error 且无 committed",
211
+ any(r.get("phase") == "outcome"
212
+ and r.get("status") == twophase.STATUS_ERROR
213
+ for r in _recs(cg7)), _recs(cg7))
214
+ check("⑦3 失败不留未结清意图(已配对)", cg7.pending_writes() == [])
215
+
216
+ # ---------- ⑧ 未落盘的分支不记账 ----------
217
+ cg8 = _mk_cg(tmp)
218
+ out8 = pipe.execute(cg8, {"content_kind": "text",
219
+ "content": "这段含 FORBIDDEN_WORD 应被拒",
220
+ "layer": "knowledge"})
221
+ check("⑧1 闸门拒绝分支:committed=False",
222
+ out8.get("committed") is False, out8.get("moved_to"))
223
+ check("⑧2 未落盘 → **不产生** intent(没打算写就不记账)",
224
+ _phases(cg8) == [], _recs(cg8))
225
+ check("⑧3 对账在空账本上恒无未结清",
226
+ cg8.reconcile_writes().get("unpaired") == 0)
227
+
228
+ # ---------- ⑨ gated 替代执行路径也成对 ----------
229
+ cg9 = _mk_cg(tmp)
230
+ out9 = pipe.execute(cg9, {"content_kind": "text",
231
+ "content": "gated 主动遗忘路径 PASSED",
232
+ "layer": "contextual", "gated": True,
233
+ "importance": 0.5})
234
+ nid9 = out9.get("id")
235
+ ph9 = _phases(cg9, nid9)
236
+ check("⑨1 gated 路径账本成对(intent → outcome)",
237
+ ph9 == ["intent", "outcome"], ph9)
238
+ oc9 = [r for r in _recs(cg9)
239
+ if r.get("id") == nid9 and r["phase"] == "outcome"][0]
240
+ check("⑨2 结局与 verdict 一致(committed 当且仅当落盘)",
241
+ (oc9.get("status") == twophase.STATUS_COMMITTED)
242
+ == bool(out9.get("committed")), (oc9, out9.get("committed")))
243
+ check("⑨3 gated 结局无未结清(自己落盘的路径也结清)",
244
+ cg9.pending_writes() == [])
245
+
246
+ # ---------- ⑩ 边界 ----------
247
+ cg10 = _mk_cg(tmp)
248
+ check("⑩1 空账本:records/pending/reconcile 均不炸",
249
+ _recs(cg10) == [] and cg10.pending_writes() == []
250
+ and cg10.reconcile_writes().get("unpaired") == 0)
251
+ bad = os.path.join(cg10.root, "_write_2pc.jsonl")
252
+ with open(bad, "a", encoding="utf-8") as f:
253
+ f.write("{坏行不是 json\n")
254
+ check("⑩2 坏行跳过不炸(账本读侧容错)",
255
+ isinstance(_recs(cg10), list))
256
+ check("⑩3 指纹算法与 nodefile 同源(唯一真源,防漂移)",
257
+ twophase.nodefile.content_hash("abc")
258
+ == __import__("hashlib").sha256(b"abc").hexdigest()[:12])
259
+ check("⑩4 账本文件在 root 下(随记忆库一起备份/迁移)",
260
+ os.path.dirname(twophase.log_path(cg10)) == cg10.root)
261
+ check("⑩5 对账只增行不删行(append-only 结构保证)",
262
+ len(_lines(cg10)) >= 1 and all(l.strip() for l in _lines(cg10)))
263
+
264
+
265
+ def main():
266
+ tmp = tempfile.mkdtemp(prefix="twophase_")
267
+ old = os.environ.pop("MDCG_POLICY_FILE", None)
268
+ try:
269
+ _run(tmp)
270
+ except Exception:
271
+ traceback.print_exc()
272
+ _fail.append("未捕获异常")
273
+ finally:
274
+ shutil.rmtree(tmp, ignore_errors=True)
275
+ if old is not None:
276
+ os.environ["MDCG_POLICY_FILE"] = old
277
+ else:
278
+ os.environ.pop("MDCG_POLICY_FILE", None)
279
+ print("\ntest_twophase: %d 通过 / %d 失败" % (_ok, len(_fail)))
280
+ if _fail:
281
+ print("失败项:" + "、".join(_fail))
282
+ raise SystemExit(1 if _fail else 0)
283
+
284
+
285
+ if __name__ == "__main__":
286
+ main()