@furongjun1999/dsh-memory 0.4.11 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (580) hide show
  1. package/README.md +552 -465
  2. package/codebuddy/CODEBUDDY.md +11 -3
  3. package/codebuddy/README.md +92 -90
  4. package/codebuddy/mcp.json +27 -27
  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 -169
  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 -144
  7. package/docs/README.md +143 -111
  8. package/docs/discipline/harnesses.yaml +244 -226
  9. package/docs/discipline/templates/full.md.tmpl +61 -61
  10. package/docs/discipline/templates/rules.mdc.tmpl +68 -0
  11. package/docs/discipline/templates/skill.md.tmpl +23 -23
  12. package/docs/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 +299 -299
  13. package/docs/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 +239 -239
  14. package/docs/eval/bench_lingshu_self/bench_self.py +140 -0
  15. package/docs/eval/bench_lingshu_self/self_bench_result.json +404 -0
  16. 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
  17. 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
  18. 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 +174 -174
  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 +422 -0
  20. 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 +223 -223
  21. 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
  22. 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
  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_v1.md +156 -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_v10.md +210 -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_v11.md +227 -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_v12.md +203 -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_v13.md +233 -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_v14.md +191 -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_v15.md +213 -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_v16.md +214 -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_v17.md +199 -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_v2.md +156 -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_v3.md +152 -0
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. package/docs/{mdcg → hive}//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 +165 -160
  41. package/docs/hive//344/273/244/347/211/214/350/257/255/344/271/211/344/277/256/346/255/243_/344/273/262/350/243/201/344/275/215_v0.1.md +76 -0
  42. package/docs/{mdcg → hive}//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 -236
  43. 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
  44. 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 -43
  45. 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 +85 -0
  46. 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 -102
  47. 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 +47 -0
  48. 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 +519 -503
  49. 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 +85 -85
  50. package/docs/hive//350/234/202/345/267/242/350/256/276/350/256/241_/347/220/206/350/256/272/345/257/271/351/275/220_v0.1.md +191 -0
  51. package/docs/hive//350/234/202/345/267/242/350/277/255/344/273/243_/345/256/217/350/247/202/344/270/216/347/276/244/344/275/223/350/260/203/345/272/246_v0.1.md +90 -0
  52. package/docs/images/lingshu-moonlight-covenant-poster-preview.jpg +0 -0
  53. package/docs/images/lingshu-moonlight-covenant-poster.png +0 -0
  54. package/docs/mdcg/D_meta_/345/267/245/347/250/213/345/214/226/346/226/271/346/241/210_v0.2.md +216 -216
  55. package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +646 -646
  56. package/docs/mdcg/lingshu_tutorial.html +14449 -14449
  57. package/docs/mdcg/release_v0.4.11.md +49 -0
  58. package/docs/mdcg/release_v0.4.5.md +55 -55
  59. package/docs/mdcg/tool_table_v0.3.0.md +117 -117
  60. 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 -600
  61. 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
  62. 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 +275 -275
  63. package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +54 -0
  64. 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 +130 -130
  65. 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 +278 -278
  66. 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 -172
  67. package/docs/mdcg//347/274/272/345/217/243/345/215/225_P0/346/224/266/345/217/243_v0.1.md +270 -270
  68. 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 +491 -491
  69. 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 +340 -340
  70. 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 +588 -588
  71. 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 -176
  72. 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 +81 -81
  73. package/docs/swarm//350/234/202/347/276/244/344/272/222/350/201/224_v0.1.md +704 -704
  74. 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 -242
  75. 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
  76. 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 -129
  77. 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 -134
  78. package/docs/theory//346/231/272/350/203/275/350/256/2723.4.md +5260 -5260
  79. 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 -145
  80. 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 +144 -144
  81. 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 +198 -198
  82. 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 -221
  83. 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 -237
  84. 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
  85. package/docs//345/217/221/345/270/203/344/273/266/345/233/236/346/272/257/350/257/264/346/230/216_v0.1.md +82 -82
  86. 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 +28 -2
  87. package/dsh/README.md +82 -82
  88. package/dsh/cordis.yml.example +139 -139
  89. package/dsh/update-lingshu.bat +11 -11
  90. package/lib/bridge.d.ts +9 -0
  91. package/lib/bridge.js +35 -0
  92. package/lib/hooks.js +36 -2
  93. package/lib/index.js +7 -1
  94. package/lib/lib/roleplay_web.js +116 -29
  95. package/lib/lib/token_store.d.ts +7 -1
  96. package/lib/lib/token_store.js +12 -3
  97. package/md_cg/__init__.py +7 -7
  98. package/md_cg/audit.py +379 -368
  99. package/md_cg/autonomy.py +287 -287
  100. package/md_cg/backfill.py +1328 -1327
  101. package/md_cg/backfill_bigdomain.py +34 -34
  102. package/md_cg/backfill_bucket_zh.py +35 -0
  103. package/md_cg/bench6_arms.py +410 -410
  104. package/md_cg/bench6_common.py +230 -230
  105. package/md_cg/bench6_competitors.py +212 -212
  106. package/md_cg/bench_axis_domain.py +257 -257
  107. package/md_cg/bench_blind_comp.py +308 -308
  108. package/md_cg/bench_e2e_judge.py +532 -0
  109. package/md_cg/bench_e2e_locomo_qa.py +368 -0
  110. package/md_cg/bench_e2e_qa.py +256 -0
  111. package/md_cg/bench_en_atoms_public.py +230 -230
  112. package/md_cg/bench_governance.py +348 -348
  113. package/md_cg/bench_lme_zh.py +410 -410
  114. package/md_cg/bench_locomo.py +121 -121
  115. package/md_cg/bench_locomo_zh.py +450 -450
  116. package/md_cg/bench_locomo_zh_public.py +147 -147
  117. package/md_cg/bench_longmem.py +112 -112
  118. package/md_cg/bench_membench.py +632 -632
  119. package/md_cg/bench_p0.py +149 -149
  120. package/md_cg/bench_progressive.py +287 -287
  121. package/md_cg/bench_role_views.py +238 -238
  122. package/md_cg/bench_task_ab.py +243 -243
  123. package/md_cg/bench_task_ab_llm.py +408 -408
  124. package/md_cg/bench_unified_en.py +204 -204
  125. package/md_cg/bench_zh_mad.py +601 -601
  126. package/md_cg/blindspot_tickets.py +123 -123
  127. package/md_cg/branches.py +301 -285
  128. package/md_cg/build_postings.py +73 -73
  129. package/md_cg/ccgc.py +1006 -948
  130. package/md_cg/census.py +132 -132
  131. package/md_cg/chain.py +315 -300
  132. package/md_cg/codeindex.py +531 -531
  133. package/md_cg/coldverify.py +292 -292
  134. package/md_cg/comment_gate.py +337 -337
  135. package/md_cg/cond_compose.py +190 -190
  136. package/md_cg/cond_facts.py +154 -154
  137. package/md_cg/cond_template.json +106 -106
  138. package/md_cg/condition_anchor.py +142 -142
  139. package/md_cg/conformance.py +726 -726
  140. package/md_cg/consistency.py +717 -717
  141. package/md_cg/consolidate.py +1537 -1439
  142. package/md_cg/corpus.py +110 -110
  143. package/md_cg/crosscheck.py +1098 -1097
  144. package/md_cg/crypto.py +3 -1
  145. package/md_cg/d_meta.py +310 -310
  146. package/md_cg/datapath.py +78 -18
  147. package/md_cg/docindex.py +473 -473
  148. package/md_cg/eval_common.py +575 -575
  149. package/md_cg/evidence.py +4 -2
  150. package/md_cg/evolution.py +477 -477
  151. package/md_cg/export.py +222 -220
  152. package/md_cg/forgetting.py +581 -581
  153. package/md_cg/fsutil.py +377 -329
  154. package/md_cg/hotcache.py +48 -7
  155. package/md_cg/hyperedge.py +251 -251
  156. package/md_cg/identity.py +390 -390
  157. package/md_cg/insight.py +500 -500
  158. package/md_cg/interop.py +338 -0
  159. package/md_cg/judgment_manifest.py +177 -0
  160. package/md_cg/lexicon/build_cedict_en_zh.py +329 -329
  161. package/md_cg/lexicon/build_standard_en.py +171 -171
  162. package/md_cg/lexicon/expand_en_zh.py +211 -211
  163. package/md_cg/lifecycle.py +272 -272
  164. package/md_cg/linkref.py +280 -280
  165. package/md_cg/links.py +140 -107
  166. package/md_cg/mcp_server.py +403 -55
  167. package/md_cg/md_whitebox.py +345 -345
  168. package/md_cg/mdcg.py +783 -233
  169. package/md_cg/mdcos.py +500 -70
  170. package/md_cg/metacognition.py +591 -591
  171. package/md_cg/migrate.py +119 -119
  172. package/md_cg/migrate_aeis.py +221 -221
  173. package/md_cg/migrate_roleplay.py +293 -293
  174. package/md_cg/migrate_wisdom_graph.py +360 -360
  175. package/md_cg/mreview/__init__.py +25 -25
  176. package/md_cg/mreview/__main__.py +110 -110
  177. package/md_cg/mreview/bundle.py +178 -178
  178. package/md_cg/mreview/candidates.py +262 -262
  179. package/md_cg/mreview/govern.py +694 -693
  180. package/md_cg/mreview/locate.py +939 -939
  181. package/md_cg/mreview/pipeline.py +728 -728
  182. package/md_cg/mreview/rules/duplication.json +21 -21
  183. package/md_cg/mreview/rules/field_coverage.json +54 -54
  184. package/md_cg/mreview/rules/source_license.json +21 -21
  185. package/md_cg/mreview/rules/template_flow.json +21 -21
  186. package/md_cg/mreview/ruleset.py +252 -252
  187. package/md_cg/nodefile.py +575 -575
  188. package/md_cg/pooling.py +484 -472
  189. package/md_cg/postings.py +300 -298
  190. package/md_cg/predict.py +1100 -1100
  191. package/md_cg/progressive.py +123 -123
  192. package/md_cg/protect.py +272 -272
  193. package/md_cg/protocol/md_cg_gate.proto +33 -33
  194. package/md_cg/protocol.py +372 -372
  195. package/md_cg/provenance.py +582 -582
  196. package/md_cg/reach.py +453 -453
  197. package/md_cg/readcache.py +143 -0
  198. package/md_cg/reconcile.py +228 -0
  199. package/md_cg/refindex.py +833 -833
  200. package/md_cg/refine.py +604 -604
  201. package/md_cg/review_cli.py +170 -0
  202. package/md_cg/roleviews.py +89 -89
  203. package/md_cg/routing.py +393 -365
  204. package/md_cg/run_tests.py +211 -0
  205. package/md_cg/scrub.py +13 -3
  206. package/md_cg/security.py +128 -18
  207. package/md_cg/self_state.py +1029 -1029
  208. package/md_cg/selfreport.py +152 -151
  209. package/md_cg/semantic/__init__.py +10 -10
  210. package/md_cg/semantic/canonical.py +122 -122
  211. package/md_cg/semantic/en_normalizer.py +364 -364
  212. package/md_cg/semantic/en_zh_map.json +28694 -0
  213. package/md_cg/semantic/export_en_zh_map.py +64 -0
  214. package/md_cg/semantic/unify.py +45 -0
  215. package/md_cg/semantic/zh_en_atoms.py +139 -139
  216. package/md_cg/signer.py +7 -4
  217. package/md_cg/sources.py +816 -582
  218. package/md_cg/statushdr.py +179 -179
  219. package/md_cg/stg.py +54 -37
  220. package/md_cg/subgraph.py +729 -729
  221. package/md_cg/sustain.py +35 -5
  222. package/md_cg/tasks.py +470 -470
  223. package/md_cg/test_access_hints.py +147 -0
  224. package/md_cg/test_action_derive.py +203 -203
  225. package/md_cg/test_audit_rotate.py +270 -270
  226. package/md_cg/test_autonomy.py +143 -143
  227. package/md_cg/test_bench_governance.py +102 -102
  228. package/md_cg/test_blindspot_tickets.py +166 -166
  229. package/md_cg/test_branch_discard_tombstone.py +136 -0
  230. package/md_cg/test_branches.py +13 -3
  231. package/md_cg/test_ccg_perturb.py +184 -184
  232. package/md_cg/test_ccgc.py +433 -433
  233. package/md_cg/test_census_prune.py +81 -81
  234. package/md_cg/test_chain_read_isolate.py +168 -0
  235. package/md_cg/test_cond_compose_anchors.py +76 -76
  236. package/md_cg/test_cond_match.py +165 -165
  237. package/md_cg/test_condition_anchor.py +81 -81
  238. package/md_cg/test_d_meta.py +412 -412
  239. package/md_cg/test_datapath_device_name.py +203 -0
  240. package/md_cg/test_datapath_root.py +199 -199
  241. package/md_cg/test_emit_negtail_cache.py +156 -0
  242. package/md_cg/test_en_pipeline.py +22 -2
  243. package/md_cg/test_gain_gate.py +212 -212
  244. package/md_cg/test_govern_directread.py +421 -0
  245. package/md_cg/test_health_scale.py +173 -173
  246. package/md_cg/test_hive_ingest.py +285 -0
  247. package/md_cg/test_hot_cold.py +215 -215
  248. package/md_cg/test_hyperedge.py +245 -245
  249. package/md_cg/test_i26_empty_first_write.py +116 -0
  250. package/md_cg/test_i27_e041_identity.py +128 -0
  251. package/md_cg/test_i28_hotcache_prodpath.py +122 -0
  252. package/md_cg/test_i32_hotcache_env_key.py +218 -0
  253. package/md_cg/test_identity_attribution.py +96 -15
  254. package/md_cg/test_index_durability.py +17 -3
  255. package/md_cg/test_interop.py +95 -0
  256. package/md_cg/test_interop_judgment.py +228 -0
  257. package/md_cg/test_issue39_utf8_stdio.py +273 -0
  258. package/md_cg/test_lifecycle.py +309 -309
  259. package/md_cg/test_linkref.py +306 -306
  260. package/md_cg/test_links_concurrent_write.py +188 -0
  261. package/md_cg/test_lock.py +43 -43
  262. package/md_cg/test_md_access_parity.py +255 -255
  263. package/md_cg/test_md_writepath.py +345 -345
  264. package/md_cg/test_mdstore_search_parity.py +160 -0
  265. package/md_cg/test_merge_upsert.py +168 -0
  266. package/md_cg/test_mr_m2.py +587 -587
  267. package/md_cg/test_mr_m3.py +710 -710
  268. package/md_cg/test_mr_m4.py +485 -485
  269. package/md_cg/test_n123_derive_expiry_chain.py +205 -0
  270. package/md_cg/test_n130_verify_falsified_protect.py +185 -0
  271. package/md_cg/test_n131_merge_gate.py +205 -0
  272. package/md_cg/test_p0.py +250 -250
  273. package/md_cg/test_p1.py +316 -316
  274. package/md_cg/test_p10_identity.py +173 -173
  275. package/md_cg/test_p11_consistency.py +233 -233
  276. package/md_cg/test_p12_metacognition.py +212 -212
  277. package/md_cg/test_p13_encryption.py +241 -241
  278. package/md_cg/test_p14_sustain.py +249 -249
  279. package/md_cg/test_p15_scrub.py +280 -280
  280. package/md_cg/test_p16_self_state.py +301 -301
  281. package/md_cg/test_p17_predict.py +354 -354
  282. package/md_cg/test_p18_whitebox.py +171 -171
  283. package/md_cg/test_p19_migrate_roleplay.py +149 -149
  284. package/md_cg/test_p1x_ref_root.py +160 -0
  285. package/md_cg/test_p20_evolution.py +315 -315
  286. package/md_cg/test_p21_tokens.py +293 -270
  287. package/md_cg/test_p22_theory.py +175 -175
  288. package/md_cg/test_p23_links.py +311 -311
  289. package/md_cg/test_p24_evidence.py +227 -227
  290. package/md_cg/test_p25_weights.py +156 -156
  291. package/md_cg/test_p26_refindex.py +416 -416
  292. package/md_cg/test_p27_docindex.py +16 -7
  293. package/md_cg/test_p28_refcheck.py +305 -305
  294. package/md_cg/test_p29_session_ingest_export.py +354 -333
  295. package/md_cg/test_p2_mcp.py +3 -0
  296. package/md_cg/test_p3.py +11 -2
  297. package/md_cg/test_p30_maintain.py +330 -330
  298. package/md_cg/test_p31_insight.py +534 -534
  299. package/md_cg/test_p32_backfill.py +7 -1
  300. package/md_cg/test_p33_ccg_wiring.py +293 -293
  301. package/md_cg/test_p34_crosscheck.py +331 -331
  302. package/md_cg/test_p35_conditioned_claim.py +252 -252
  303. package/md_cg/test_p36_kp_align.py +230 -230
  304. package/md_cg/test_p37_condition_space.py +248 -248
  305. package/md_cg/test_p38_concurrent_flush.py +102 -0
  306. package/md_cg/test_p38_contextualize.py +273 -273
  307. package/md_cg/test_p39_verify_flow.py +153 -0
  308. package/md_cg/test_p39_vision_evidence.py +369 -369
  309. package/md_cg/test_p40_refine_worklist.py +241 -241
  310. package/md_cg/test_p41_evolve_patrol.py +224 -224
  311. package/md_cg/test_p42_provenance.py +269 -269
  312. package/md_cg/test_p43_pooling.py +412 -398
  313. package/md_cg/test_p44_md_whitebox.py +231 -231
  314. package/md_cg/test_p45_session_identity.py +219 -219
  315. package/md_cg/test_p46_unit_scope.py +272 -272
  316. package/md_cg/test_p47_session_view.py +316 -0
  317. package/md_cg/test_p4_fuzzy.py +223 -223
  318. package/md_cg/test_p5_semantic.py +226 -226
  319. package/md_cg/test_p6_consolidate.py +440 -387
  320. package/md_cg/test_p7_goals_recent.py +202 -202
  321. package/md_cg/test_p8_subgraph_chain.py +200 -200
  322. package/md_cg/test_p9_forget_protect.py +231 -231
  323. package/md_cg/test_predict_beta.py +135 -135
  324. package/md_cg/test_preflight_failclosed.py +100 -100
  325. package/md_cg/test_progressive.py +146 -146
  326. package/md_cg/test_propose_tail_index.py +157 -0
  327. package/md_cg/test_protocol.py +243 -243
  328. package/md_cg/test_reach.py +378 -378
  329. package/md_cg/test_reach_keys.py +201 -201
  330. package/md_cg/test_read_clip.py +141 -141
  331. package/md_cg/test_read_scope_b27.py +277 -0
  332. package/md_cg/test_readcache_default_on.py +168 -0
  333. package/md_cg/test_readcache_precise_inval.py +270 -0
  334. package/md_cg/test_readcache_prodpath.py +203 -0
  335. package/md_cg/test_reconcile_v0.py +294 -0
  336. package/md_cg/test_retr_gates_prodpath.py +140 -0
  337. package/md_cg/test_retr_s1.py +344 -340
  338. package/md_cg/test_retr_s1b.py +276 -209
  339. package/md_cg/test_retr_s3.py +194 -194
  340. package/md_cg/test_retr_s4.py +163 -163
  341. package/md_cg/test_retr_s5.py +200 -200
  342. package/md_cg/test_retr_s6.py +157 -157
  343. package/md_cg/test_retr_s7.py +392 -384
  344. package/md_cg/test_retr_s8_time.py +369 -316
  345. package/md_cg/test_retr_s9_edges.py +286 -286
  346. package/md_cg/test_retr_s9_entity_ctx.py +9 -3
  347. package/md_cg/test_retr_score_once.py +208 -0
  348. package/md_cg/test_review_conformance.py +367 -367
  349. package/md_cg/test_review_onepass.py +170 -0
  350. package/md_cg/test_role_views.py +354 -354
  351. package/md_cg/test_rrf_graph_seed_cache.py +154 -0
  352. package/md_cg/test_security_audit.py +155 -0
  353. package/md_cg/test_security_audit_b26.py +161 -0
  354. package/md_cg/test_security_audit_v21.py +250 -0
  355. package/md_cg/test_sem_noise.py +242 -242
  356. package/md_cg/test_semantic_canonical.py +16 -2
  357. package/md_cg/test_session_isolation.py +168 -0
  358. package/md_cg/test_snapshot_autoclose.py +187 -0
  359. package/md_cg/test_subproc_encoding.py +192 -192
  360. package/md_cg/test_sustain_mutual.py +153 -153
  361. package/md_cg/test_tail_watermark_race.py +208 -0
  362. package/md_cg/test_tasks.py +409 -409
  363. package/md_cg/test_tenant_env_override_warn.py +139 -0
  364. package/md_cg/test_tenant_registry_corrupt_warn.py +151 -0
  365. package/md_cg/test_tool_face.py +189 -189
  366. package/md_cg/test_transfer.py +180 -180
  367. package/md_cg/test_trust.py +361 -361
  368. package/md_cg/test_twophase.py +286 -286
  369. package/md_cg/test_v14_fixes.py +38 -20
  370. package/md_cg/test_validity_filter.py +280 -280
  371. package/md_cg/test_verify_answer.py +138 -138
  372. package/md_cg/test_verify_dirty_reconcile.py +157 -0
  373. package/md_cg/test_wisdom_md_store.py +292 -292
  374. package/md_cg/test_writelimit.py +197 -197
  375. package/md_cg/test_writepipe.py +214 -214
  376. package/md_cg/theory.py +6 -3
  377. package/md_cg/tokens.py +85 -14
  378. package/md_cg/tool_face.py +260 -260
  379. package/md_cg/trust.py +986 -950
  380. package/md_cg/twophase.py +231 -231
  381. package/md_cg/units.py +668 -667
  382. package/md_cg/vision_evidence.py +667 -666
  383. package/md_cg/weights.py +624 -624
  384. package/md_cg/whitebox.py +527 -527
  385. package/md_cg/whitebox_kb/__init__.py +37 -37
  386. package/md_cg/whitebox_kb/aeis_core/__init__.py +42 -42
  387. package/md_cg/whitebox_kb/aeis_core/semantic.py +280 -280
  388. package/md_cg/whitebox_kb/aeis_core/textutil.py +13 -13
  389. package/md_cg/whitebox_kb/engine.py +310 -310
  390. package/md_cg/whitebox_kb/seed_knowledge//346/231/272/350/203/275/350/256/2723.4.md +5260 -5260
  391. package/md_cg/whitebox_kb/wisdom/browser_units.py +2631 -2631
  392. package/md_cg/whitebox_kb/wisdom/causal_discover.py +432 -432
  393. package/md_cg/whitebox_kb/wisdom/chat_engine.py +1506 -1506
  394. package/md_cg/whitebox_kb/wisdom/compiler_code_units.py +3033 -3033
  395. package/md_cg/whitebox_kb/wisdom/condition_algebra.py +112 -112
  396. package/md_cg/whitebox_kb/wisdom/condition_frame.py +315 -315
  397. package/md_cg/whitebox_kb/wisdom/condition_kb.py +108 -108
  398. package/md_cg/whitebox_kb/wisdom/conflict_map.json +4445 -4445
  399. package/md_cg/whitebox_kb/wisdom/core/lexer.py +512 -512
  400. package/md_cg/whitebox_kb/wisdom/core/name_checker.py +1023 -1023
  401. package/md_cg/whitebox_kb/wisdom/cspmn.py +258 -258
  402. package/md_cg/whitebox_kb/wisdom/csre.py +264 -264
  403. package/md_cg/whitebox_kb/wisdom/danmaku_audit.py +252 -252
  404. package/md_cg/whitebox_kb/wisdom/distilled_condition_units.json +6417 -6417
  405. package/md_cg/whitebox_kb/wisdom/docs/WB-EVAL-20260902b.json +1950 -1950
  406. package/md_cg/whitebox_kb/wisdom/docs/WB-EVAL-20260902c.json +1296 -1296
  407. package/md_cg/whitebox_kb/wisdom/docs/whitebox_capability_graph_demo.json +59 -59
  408. package/md_cg/whitebox_kb/wisdom/graph_db_units.py +3089 -3089
  409. package/md_cg/whitebox_kb/wisdom/knowledge_points.py +318 -318
  410. package/md_cg/whitebox_kb/wisdom/md_access.py +470 -470
  411. package/md_cg/whitebox_kb/wisdom/md_store.py +276 -251
  412. package/md_cg/whitebox_kb/wisdom/migrate_wisdom.py +476 -476
  413. package/md_cg/whitebox_kb/wisdom/multilang_ir.py +128 -124
  414. package/md_cg/whitebox_kb/wisdom/navigate.py +248 -248
  415. package/md_cg/whitebox_kb/wisdom/neural_retrieve.py +224 -224
  416. package/md_cg/whitebox_kb/wisdom/os_units.py +2735 -2735
  417. package/md_cg/whitebox_kb/wisdom/pattern_separation.py +376 -376
  418. package/md_cg/whitebox_kb/wisdom/prereq_map.json +364 -364
  419. package/md_cg/whitebox_kb/wisdom/python_code_units.py +2766 -2766
  420. package/md_cg/whitebox_kb/wisdom/role_solidified.json +7 -7
  421. package/md_cg/whitebox_kb/wisdom/route_memory.py +244 -244
  422. package/md_cg/whitebox_kb/wisdom/scene_reconstruction.py +148 -148
  423. package/md_cg/whitebox_kb/wisdom/snr_report.json +40 -40
  424. package/md_cg/whitebox_kb/wisdom/test_code_compose_domains.py +7541 -7541
  425. package/md_cg/whitebox_kb/wisdom/test_compiler_self_bootstrap.py +354 -354
  426. package/md_cg/whitebox_kb/wisdom/test_ecosystem_assembly.py +158 -158
  427. package/md_cg/whitebox_kb/wisdom/test_ecosystem_demos.py +193 -193
  428. package/md_cg/whitebox_kb/wisdom/test_graph_db.py +292 -292
  429. package/md_cg/whitebox_kb/wisdom/test_python_self_bootstrap.py +160 -160
  430. package/md_cg/whitebox_kb/wisdom/trigger_words_index.json +4366 -4366
  431. package/md_cg/whitebox_kb/wisdom/verifier.py +1297 -1297
  432. package/md_cg/writelimit.py +356 -356
  433. package/md_cg/writepipe.py +20 -8
  434. package/package.json +101 -96
  435. package/skills/plugin.json +54 -54
  436. package/skills/skills/designer-perspective/SKILL.md +158 -158
  437. package/skills/skills/designer-perspective/references/01-observation-position.md +66 -66
  438. package/skills/skills/designer-perspective/references/02-structure-recognition.md +62 -62
  439. package/skills/skills/designer-perspective/references/03-direction-judgment.md +55 -55
  440. package/skills/skills/designer-perspective/references/04-qualification-verdict.md +72 -72
  441. package/skills/skills/designer-perspective/references/05-condition-attribution.md +74 -74
  442. package/skills/skills/designer-perspective/scripts/designer.py +545 -545
  443. package/skills/skills/designer-perspective/tests/cases.jsonl +17 -17
  444. package/skills/skills/designer-perspective/tests/selftest.py +61 -61
  445. package/skills/skills/lingshu-browser/SKILL.md +60 -60
  446. package/skills/skills/lingshu-compiler/SKILL.md +56 -56
  447. package/skills/skills/lingshu-compiler/units/analyze-type-infer/SKILL.md +45 -45
  448. package/skills/skills/lingshu-compiler/units/check-name-real/SKILL.md +45 -45
  449. package/skills/skills/lingshu-compiler/units/compile-assign/SKILL.md +45 -45
  450. package/skills/skills/lingshu-compiler/units/compile-expr-tree/SKILL.md +45 -45
  451. package/skills/skills/lingshu-compiler/units/compile-full-pipeline/SKILL.md +45 -45
  452. package/skills/skills/lingshu-compiler/units/compile-func-def/SKILL.md +45 -45
  453. package/skills/skills/lingshu-compiler/units/compile-if-then/SKILL.md +45 -45
  454. package/skills/skills/lingshu-compiler/units/compile-logic-expr/SKILL.md +45 -45
  455. package/skills/skills/lingshu-compiler/units/compile-recursive/SKILL.md +45 -45
  456. package/skills/skills/lingshu-compiler/units/compile-scope/SKILL.md +45 -45
  457. package/skills/skills/lingshu-compiler/units/compile-type-check/SKILL.md +45 -45
  458. package/skills/skills/lingshu-compiler/units/compile-while/SKILL.md +45 -45
  459. package/skills/skills/lingshu-compiler/units/compiler-0010c4bf/SKILL.md +45 -45
  460. package/skills/skills/lingshu-compiler/units/compiler-0355bffb/SKILL.md +45 -45
  461. package/skills/skills/lingshu-compiler/units/compiler-0361708a/SKILL.md +45 -45
  462. package/skills/skills/lingshu-compiler/units/compiler-054a0414/SKILL.md +45 -45
  463. package/skills/skills/lingshu-compiler/units/compiler-05a1691a/SKILL.md +45 -45
  464. package/skills/skills/lingshu-compiler/units/compiler-05eeed1e/SKILL.md +45 -45
  465. package/skills/skills/lingshu-compiler/units/compiler-0622a1f6/SKILL.md +45 -45
  466. package/skills/skills/lingshu-compiler/units/compiler-08b54217/SKILL.md +45 -45
  467. package/skills/skills/lingshu-compiler/units/compiler-0a62b70c/SKILL.md +45 -45
  468. package/skills/skills/lingshu-compiler/units/compiler-0ab24d00/SKILL.md +45 -45
  469. package/skills/skills/lingshu-compiler/units/compiler-0e093688/SKILL.md +45 -45
  470. package/skills/skills/lingshu-compiler/units/compiler-0e82b966/SKILL.md +45 -45
  471. package/skills/skills/lingshu-compiler/units/compiler-0ee9b9b9/SKILL.md +45 -45
  472. package/skills/skills/lingshu-compiler/units/compiler-0f3787e6/SKILL.md +45 -45
  473. package/skills/skills/lingshu-compiler/units/compiler-1028685f/SKILL.md +45 -45
  474. package/skills/skills/lingshu-compiler/units/compiler-11897630/SKILL.md +45 -45
  475. package/skills/skills/lingshu-compiler/units/compiler-16661b9b/SKILL.md +45 -45
  476. package/skills/skills/lingshu-compiler/units/compiler-1f722303/SKILL.md +45 -45
  477. package/skills/skills/lingshu-compiler/units/compiler-25be1262/SKILL.md +45 -45
  478. package/skills/skills/lingshu-compiler/units/compiler-2a76ba07/SKILL.md +45 -45
  479. package/skills/skills/lingshu-compiler/units/compiler-2dbea54a/SKILL.md +45 -45
  480. package/skills/skills/lingshu-compiler/units/compiler-2df52f16/SKILL.md +45 -45
  481. package/skills/skills/lingshu-compiler/units/compiler-2ec2c9d2/SKILL.md +45 -45
  482. package/skills/skills/lingshu-compiler/units/compiler-2f8c8f39/SKILL.md +45 -45
  483. package/skills/skills/lingshu-compiler/units/compiler-38378ac7/SKILL.md +45 -45
  484. package/skills/skills/lingshu-compiler/units/compiler-39457d2e/SKILL.md +45 -45
  485. package/skills/skills/lingshu-compiler/units/compiler-39fb5926/SKILL.md +45 -45
  486. package/skills/skills/lingshu-compiler/units/compiler-47f4fbfa/SKILL.md +45 -45
  487. package/skills/skills/lingshu-compiler/units/compiler-4a8cd1f1/SKILL.md +45 -45
  488. package/skills/skills/lingshu-compiler/units/compiler-4b230b6b/SKILL.md +45 -45
  489. package/skills/skills/lingshu-compiler/units/compiler-4cbbda95/SKILL.md +45 -45
  490. package/skills/skills/lingshu-compiler/units/compiler-4d5a68ff/SKILL.md +45 -45
  491. package/skills/skills/lingshu-compiler/units/compiler-56dc9bdd/SKILL.md +45 -45
  492. package/skills/skills/lingshu-compiler/units/compiler-57e76ebe/SKILL.md +45 -45
  493. package/skills/skills/lingshu-compiler/units/compiler-61ff016b/SKILL.md +45 -45
  494. package/skills/skills/lingshu-compiler/units/compiler-63eae588/SKILL.md +45 -45
  495. package/skills/skills/lingshu-compiler/units/compiler-64c4224b/SKILL.md +45 -45
  496. package/skills/skills/lingshu-compiler/units/compiler-66377955/SKILL.md +45 -45
  497. package/skills/skills/lingshu-compiler/units/compiler-674ab4f6/SKILL.md +45 -45
  498. package/skills/skills/lingshu-compiler/units/compiler-6af76fd6/SKILL.md +45 -45
  499. package/skills/skills/lingshu-compiler/units/compiler-6d979db2/SKILL.md +45 -45
  500. package/skills/skills/lingshu-compiler/units/compiler-6de326c0/SKILL.md +45 -45
  501. package/skills/skills/lingshu-compiler/units/compiler-6f1c0eea/SKILL.md +45 -45
  502. package/skills/skills/lingshu-compiler/units/compiler-724d6c9b/SKILL.md +45 -45
  503. package/skills/skills/lingshu-compiler/units/compiler-7690177f/SKILL.md +45 -45
  504. package/skills/skills/lingshu-compiler/units/compiler-7b229a7d/SKILL.md +45 -45
  505. package/skills/skills/lingshu-compiler/units/compiler-7f471b3a/SKILL.md +45 -45
  506. package/skills/skills/lingshu-compiler/units/compiler-815cad08/SKILL.md +45 -45
  507. package/skills/skills/lingshu-compiler/units/compiler-83c61634/SKILL.md +45 -45
  508. package/skills/skills/lingshu-compiler/units/compiler-8c798c81/SKILL.md +45 -45
  509. package/skills/skills/lingshu-compiler/units/compiler-8dd747ac/SKILL.md +45 -45
  510. package/skills/skills/lingshu-compiler/units/compiler-90324d0a/SKILL.md +45 -45
  511. package/skills/skills/lingshu-compiler/units/compiler-94b8d72d/SKILL.md +45 -45
  512. package/skills/skills/lingshu-compiler/units/compiler-94f12231/SKILL.md +45 -45
  513. package/skills/skills/lingshu-compiler/units/compiler-95937c16/SKILL.md +45 -45
  514. package/skills/skills/lingshu-compiler/units/compiler-98a5625b/SKILL.md +45 -45
  515. package/skills/skills/lingshu-compiler/units/compiler-98b4c42f/SKILL.md +45 -45
  516. package/skills/skills/lingshu-compiler/units/compiler-98e3894a/SKILL.md +45 -45
  517. package/skills/skills/lingshu-compiler/units/compiler-9bdfe4b8/SKILL.md +45 -45
  518. package/skills/skills/lingshu-compiler/units/compiler-9d9b4e83/SKILL.md +45 -45
  519. package/skills/skills/lingshu-compiler/units/compiler-9f7be5ad/SKILL.md +45 -45
  520. package/skills/skills/lingshu-compiler/units/compiler-a6daf076/SKILL.md +45 -45
  521. package/skills/skills/lingshu-compiler/units/compiler-a7995a0c/SKILL.md +45 -45
  522. package/skills/skills/lingshu-compiler/units/compiler-a8399248/SKILL.md +45 -45
  523. package/skills/skills/lingshu-compiler/units/compiler-aabbd099/SKILL.md +45 -45
  524. package/skills/skills/lingshu-compiler/units/compiler-afe169d8/SKILL.md +45 -45
  525. package/skills/skills/lingshu-compiler/units/compiler-b0678bda/SKILL.md +45 -45
  526. package/skills/skills/lingshu-compiler/units/compiler-b09bd196/SKILL.md +45 -45
  527. package/skills/skills/lingshu-compiler/units/compiler-b1396e23/SKILL.md +45 -45
  528. package/skills/skills/lingshu-compiler/units/compiler-b9b31ce0/SKILL.md +45 -45
  529. package/skills/skills/lingshu-compiler/units/compiler-c10264a7/SKILL.md +45 -45
  530. package/skills/skills/lingshu-compiler/units/compiler-cb1e8e4b/SKILL.md +45 -45
  531. package/skills/skills/lingshu-compiler/units/compiler-ccafd438/SKILL.md +45 -45
  532. package/skills/skills/lingshu-compiler/units/compiler-cda9c262/SKILL.md +45 -45
  533. package/skills/skills/lingshu-compiler/units/compiler-ce648068/SKILL.md +45 -45
  534. package/skills/skills/lingshu-compiler/units/compiler-cf5776a4/SKILL.md +45 -45
  535. package/skills/skills/lingshu-compiler/units/compiler-d974e5d3/SKILL.md +45 -45
  536. package/skills/skills/lingshu-compiler/units/compiler-e3979fd3/SKILL.md +45 -45
  537. package/skills/skills/lingshu-compiler/units/compiler-eb1cf2b5/SKILL.md +45 -45
  538. package/skills/skills/lingshu-compiler/units/compiler-ecb30d5b/SKILL.md +45 -45
  539. package/skills/skills/lingshu-compiler/units/compiler-f8c8b24b/SKILL.md +45 -45
  540. package/skills/skills/lingshu-compiler/units/compiler-f99fedbe/SKILL.md +45 -45
  541. package/skills/skills/lingshu-compiler/units/compiler-fa8ff5f7/SKILL.md +45 -45
  542. package/skills/skills/lingshu-compiler/units/compiler-fe1b058d/SKILL.md +45 -45
  543. package/skills/skills/lingshu-compiler/units/lex-chinese-program/SKILL.md +45 -45
  544. package/skills/skills/lingshu-compiler/units/lex-dao-de-jing/SKILL.md +45 -45
  545. package/skills/skills/lingshu-compiler/units/lex-nine-chapters/SKILL.md +45 -45
  546. package/skills/skills/lingshu-compiler/units/vm-arithmetic/SKILL.md +45 -45
  547. package/skills/skills/lingshu-compiler/units/vm-array-ops/SKILL.md +45 -45
  548. package/skills/skills/lingshu-compiler/units/vm-closure-call/SKILL.md +45 -45
  549. package/skills/skills/lingshu-compiler/units/vm-closure-create/SKILL.md +45 -45
  550. package/skills/skills/lingshu-compiler/units/vm-compare/SKILL.md +45 -45
  551. package/skills/skills/lingshu-compiler/units/vm-cond-jump/SKILL.md +45 -45
  552. package/skills/skills/lingshu-compiler/units/vm-cond-space/SKILL.md +45 -45
  553. package/skills/skills/lingshu-compiler/units/vm-exception/SKILL.md +45 -45
  554. package/skills/skills/lingshu-compiler/units/vm-func-call/SKILL.md +45 -45
  555. package/skills/skills/lingshu-compiler/units/vm-loop-run/SKILL.md +45 -45
  556. package/skills/skills/lingshu-compiler/units/vm-profiling/SKILL.md +45 -45
  557. package/skills/skills/lingshu-compiler/units/vm-refcount/SKILL.md +45 -45
  558. package/skills/skills/lingshu-compiler/units/vm-run-loop/SKILL.md +45 -45
  559. package/skills/skills/lingshu-compiler/units/vm-short-circuit/SKILL.md +45 -45
  560. package/skills/skills/lingshu-compiler/units/vm-stack-guard/SKILL.md +45 -45
  561. package/skills/skills/lingshu-compiler/units/vm-stack-ops/SKILL.md +45 -45
  562. package/skills/skills/lingshu-compiler/units/vm-trust-accum/SKILL.md +45 -45
  563. package/skills/skills/lingshu-graph/SKILL.md +63 -63
  564. package/skills/skills/lingshu-net/SKILL.md +48 -48
  565. package/skills/skills/lingshu-os/SKILL.md +64 -64
  566. package/skills/skills/lingshu-pylang/SKILL.md +71 -71
  567. package/src/bridge.ts +33 -0
  568. package/src/hooks.ts +38 -2
  569. package/src/index.ts +526 -518
  570. package/src/lib/datapath.ts +326 -326
  571. package/src/lib/mdcg_client.ts +413 -413
  572. package/src/lib/mutual.ts +428 -428
  573. package/src/lib/prompt_safety.ts +62 -62
  574. package/src/lib/python_path.ts +71 -71
  575. package/src/lib/roleplay_web.ts +116 -29
  576. package/src/lib/token_store.ts +13 -3
  577. package/src/tools.ts +212 -212
  578. package/zcode/AGENTS.md +11 -3
  579. package/zcode/README.md +41 -41
  580. /package/docs/{mdcg → hive}//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" +0 -0
@@ -1,583 +1,583 @@
1
- # -*- coding: utf-8 -*-
2
- """派生溯源(G8):新增节点常态化建链 + 悬空可检出。
3
-
4
- 回答「这个节点**从哪来**」——与 `md_cg.links` 刻意分层:
5
- · `links.py` = 跨节点信任(「我信你多少」,P_trust,落 `~/.mdcg/_links.json`);
6
- · 本模块 = 节点派生关系(「它由谁派生」,落 `<root>/_link.jsonl`)。
7
- 两者都叫「链接」,但一个管**信任状态**、一个管**演进血缘**,不可混用。
8
-
9
- 存储形态(对齐「md 单一真相源 + 派生索引可重建」):
10
- · 权威声明在节点 frontmatter(`derived_from` / `derived_relation`);
11
- · `<root>/_link.jsonl` 是 **append-only 派生台账**(快查用,可由 frontmatter 重建);
12
- · 建链失败写 `<root>/_link.jsonl.fail`(降级留痕)。
13
-
14
- 三条纪律(对齐 G8 裁定 §六):
15
- 1. **只对新增节点常态化建链,历史不回填**——`rebuild_ledger` 只重放 frontmatter
16
- 里**已经声明**的关系,不为历史节点发明任何边(当前库历史声明为 0 → 重建为空);
17
- 2. **建链失败不得阻断写入**——`record()` 永不抛(best-effort),失败降级为告警 +
18
- 失败台账留痕,节点写入照常提交;
19
- 3. **巡检只读**——`check()` 检出悬空边(目标/子节点不在索引内)但**不自动删边**,
20
- 关系事实去留由人处置。
21
-
22
- 零第三方依赖。
23
- """
24
- from __future__ import annotations
25
-
26
- import json
27
- import os
28
- import time
29
-
30
- from . import trust as _trust
31
- from .fsutil import FileLock, append_jsonl, atomic_write, read_jsonl
32
-
33
- LEDGER_NAME = "_link.jsonl"
34
- FAIL_SUFFIX = ".fail"
35
- LEDGER_ENV = "MDCG_LINK_FILE"
36
- SCHEMA = 1
37
-
38
- #: 允许的派生关系(显式枚举,避免「自由字符串」把血缘写成噪声)
39
- RELATIONS = ("derived_from", "split_from", "extracted_from",
40
- "merged_from", "refined_from", "source")
41
- DEFAULT_RELATION = "derived_from"
42
-
43
- #: frontmatter 里承载派生声明的字段(写路径只读这两处,不猜)
44
- FM_FIELD = "derived_from"
45
- FM_REL_FIELD = "derived_relation"
46
-
47
- _LOCK_TIMEOUT = 2.0
48
-
49
-
50
- class ProvenanceError(Exception):
51
- """派生溯源错误。写路径侧一律由 `record()` 兜住,不向上抛。"""
52
-
53
-
54
- # --------------------------------------------------------------------------
55
- # 路径 / 规范化
56
- # --------------------------------------------------------------------------
57
-
58
- # 生效条件:path 为真值时返回 path;否则 os.environ.get(LEDGER_ENV) 为非空真值时返回该环境变量值;否则返回 os.path.join(root, LEDGER_NAME)。
59
- def ledger_file(root: str, path: str = None) -> str:
60
- """台账路径:显式 → MDCG_LINK_FILE → <root>/_link.jsonl。"""
61
- return path or os.environ.get(LEDGER_ENV) or os.path.join(root, LEDGER_NAME)
62
-
63
-
64
- # 生效条件:传入 root(path 为真值则以其为准,否则由 ledger_file 的回落决定落点)时返回 ledger_file(root, path) 结果拼接 FAIL_SUFFIX。
65
- def fail_log_file(root: str, path: str = None) -> str:
66
- """降级留痕路径(台账写不进时的「本该建的边」)。"""
67
- return ledger_file(root, path) + FAIL_SUFFIX
68
-
69
-
70
- # 生效条件:value 为 None 返回 [];否则 list/tuple/set 逐个元素、其他类型视作单元素,元素经 str(x).strip() 后非空且未出现过才保留(重复只留首次)。
71
- def as_list(value) -> list:
72
- """把单值 / 序列统一成去重、去空白的字符串列表。"""
73
- if value is None:
74
- return []
75
- items = list(value) if isinstance(value, (list, tuple, set)) else [value]
76
- out = []
77
- for x in items:
78
- s = str(x).strip()
79
- if s and s not in out:
80
- out.append(s)
81
- return out
82
-
83
-
84
- # 生效条件:rel 为假值(None/空串)或 str(rel).strip().lower() 后为空白的串(如 " ")时 r 回落 default(默认常量 DEFAULT_RELATION);r 不在 RELATIONS 内(含回落后的 default 本身非法)即抛 ProvenanceError,否则返回该小写串。
85
- def normalize_relation(rel, default: str = DEFAULT_RELATION) -> str:
86
- """严格校验关系名;非法抛 `ProvenanceError`(显式 API 用)。"""
87
- r = str(rel or "").strip().lower()
88
- if not r:
89
- r = default
90
- if r not in RELATIONS:
91
- raise ProvenanceError(f"未知派生关系:{rel}(允许:{RELATIONS})")
92
- return r
93
-
94
-
95
- # 生效条件:normalize_relation(rel, default) 抛 ProvenanceError(含 rel 为假值/仅空白且 default 非法时)则原样返回 default;否则返回该调用的返回值。
96
- def coerce_relation(rel, default: str = DEFAULT_RELATION) -> str:
97
- """宽松兜底:非法关系名回退默认值(**写路径用,保证永不阻断写入**)。"""
98
- try:
99
- return normalize_relation(rel, default)
100
- except ProvenanceError:
101
- return default
102
-
103
-
104
- # 生效条件:child 或 parent 为假值或仅空白使 str(x or "").strip() 为空、或二者 strip 后相等(自环)时返回 None;否则返回含 schema/t/child/parent/rel/batch/actor 的 dict,note 为真值时才附上并截断到 200 字符。
105
- def make_edge(child, parent, *, relation=DEFAULT_RELATION, batch=None,
106
- actor="system", note=None, t=None):
107
- """构造一条派生边;自环 / 空端点返回 None(**不产生无意义边**)。"""
108
- c, p = str(child or "").strip(), str(parent or "").strip()
109
- if not c or not p or c == p:
110
- return None
111
- edge = {"schema": SCHEMA,
112
- "t": float(t if t is not None else time.time()),
113
- "child": c, "parent": p, "rel": coerce_relation(relation),
114
- "batch": batch, "actor": actor}
115
- if note:
116
- edge["note"] = str(note)[:200]
117
- return edge
118
-
119
-
120
- # 生效条件:对 as_list(parents) 的每个父项调用 make_edge,只保留返回非 None 的边,全部被丢弃时返回空列表。
121
- def edges_for(child, parents, **kw) -> list:
122
- """`(child, [parents]) → [edge]`:空端点 / 自环自动丢弃。"""
123
- out = []
124
- for p in as_list(parents):
125
- e = make_edge(child, p, **kw)
126
- if e:
127
- out.append(e)
128
- return out
129
-
130
-
131
- # --------------------------------------------------------------------------
132
- # 写:台账追加(record 为写路径唯一入口,永不抛)
133
- # --------------------------------------------------------------------------
134
-
135
- # 生效条件:edges 为 None/空/全为假元素时返回 0;否则在 FileLock(p, timeout=_LOCK_TIMEOUT) 内逐条 append_jsonl,抛 OSError 时转抛 ProvenanceError,成功返回 len(edges)。
136
- def append(root: str, edges, *, path: str = None) -> int:
137
- """台账追加(加锁串行,防 Windows 并发交错丢边)。IO 失败抛 `ProvenanceError`。"""
138
- edges = [e for e in (edges or []) if e]
139
- if not edges:
140
- return 0
141
- p = ledger_file(root, path)
142
- try:
143
- os.makedirs(os.path.dirname(os.path.abspath(p)) or ".", exist_ok=True)
144
- with FileLock(p, timeout=_LOCK_TIMEOUT):
145
- for e in edges:
146
- append_jsonl(p, e)
147
- except OSError as exc:
148
- raise ProvenanceError(f"派生台账写入失败:{p}({exc})") from exc
149
- return len(edges)
150
-
151
-
152
- # 生效条件:给定 root/path/child/parents/relation/reason/code 即恒返回 {'ok': False, 'added': 0, 'edges': [], 'degraded': True, 'degrade_code': code, 'reason': reason},其中写 fail_log_file 台账的异常被 except Exception 吞掉。
153
- def _degrade(root: str, path, child, parents, relation, reason,
154
- code: str) -> dict:
155
- """降级留痕:把「本该建的边」记进 .fail 台账(自身也 best-effort)。"""
156
- rec = {"t": time.time(), "code": code, "child": str(child or ""),
157
- "parents": as_list(parents), "rel": str(relation or ""),
158
- "reason": str(reason)[:300]}
159
- try:
160
- append_jsonl(fail_log_file(root, path), rec)
161
- except Exception: # noqa: BLE001
162
- pass
163
- return {"ok": False, "added": 0, "edges": [], "degraded": True,
164
- "degrade_code": code, "reason": reason}
165
-
166
-
167
- # 生效条件:edges_for 抛异常时经 _degrade(code='bad_edge') 返回;edges 为空时返回 {'ok': True, 'added': 0, 'edges': [], 'reason': 'no_parents'};append 抛 ProvenanceError 或其他异常时经 _degrade(code='ledger_io') 返回;其余返回 {'ok': True, 'added': n, 'edges': edges, 'ledger': …},恒不向外抛。
168
- def record(root: str, child, parents, *, relation=DEFAULT_RELATION,
169
- batch=None, actor="system", note=None, path: str = None) -> dict:
170
- """写路径建链入口:**永不抛**(G8 硬约束:建链失败不得阻断节点写入)。
171
-
172
- 返回 `{ok, added, edges, ...}`;失败时 `ok=False` + `degraded=True` 且已写
173
- `.fail` 留痕。调用方**不得**因本函数返回 False 而回滚节点。
174
- """
175
- try:
176
- edges = edges_for(child, parents, relation=relation, batch=batch,
177
- actor=actor, note=note)
178
- except Exception as exc: # noqa: BLE001
179
- return _degrade(root, path, child, parents, relation,
180
- f"{type(exc).__name__}: {exc}", "bad_edge")
181
- if not edges:
182
- return {"ok": True, "added": 0, "edges": [], "reason": "no_parents"}
183
- try:
184
- n = append(root, edges, path=path)
185
- except ProvenanceError as exc:
186
- return _degrade(root, path, child, parents, relation, str(exc),
187
- "ledger_io")
188
- except Exception as exc: # noqa: BLE001
189
- return _degrade(root, path, child, parents, relation,
190
- f"{type(exc).__name__}: {exc}", "ledger_io")
191
- return {"ok": True, "added": n, "edges": edges,
192
- "ledger": ledger_file(root, path)}
193
-
194
-
195
- # --------------------------------------------------------------------------
196
- # 读:台账 / 索引 / 悬空巡检
197
- # --------------------------------------------------------------------------
198
-
199
- # 生效条件:遍历 read_jsonl(ledger_file(root, path)),仅收录 isinstance(r, dict) 且 r.get("child") 与 r.get("parent") 均为真值(键缺失或值为假即丢弃)的记录。
200
- def load(root: str, *, path: str = None) -> list:
201
- """读台账(跳过坏行;只取有端点的记录)。"""
202
- out = []
203
- for r in read_jsonl(ledger_file(root, path)):
204
- if isinstance(r, dict) and r.get("child") and r.get("parent"):
205
- out.append(r)
206
- return out
207
-
208
-
209
- # 生效条件:rows 中每行按 (r.get("child"), r.get("parent"), r.get("rel")) 三元组判重,仅首次出现的行保留,按原顺序返回去重列表。
210
- def _dedupe(rows):
211
- out, seen = [], set()
212
- for r in rows:
213
- key = (r.get("child"), r.get("parent"), r.get("rel"))
214
- if key in seen:
215
- continue
216
- seen.add(key)
217
- out.append(r)
218
- return out
219
-
220
-
221
- # 生效条件:child/parent/relation/batch 各为真值时才做对应等值过滤(假值或 None 不过滤),去重后 limit 为真值时返回 out[:int(limit)],limit 为 None 或 0 时返回全部 out。
222
- def edges(root: str, *, child=None, parent=None, relation=None, batch=None,
223
- limit: int = None, path: str = None) -> list:
224
- """按端点 / 关系 / 批次过滤台账边(只读,去重,保持写入顺序)。"""
225
- out = []
226
- for r in load(root, path=path):
227
- if child and r.get("child") != child:
228
- continue
229
- if parent and r.get("parent") != parent:
230
- continue
231
- if relation and r.get("rel") != relation:
232
- continue
233
- if batch and r.get("batch") != batch:
234
- continue
235
- out.append(r)
236
- out = _dedupe(out)
237
- return out[:int(limit)] if limit else out
238
-
239
-
240
- # 生效条件:以 (getattr(cg, "index", None) or {}).get("nodes") or {} 遍历(缺失时视作空);prefix 为真值时仅保留 str(nid).startswith(prefix) 的节点,输出经 _dedupe 去重。
241
- def index_edges(cg, *, prefix: str = None) -> list:
242
- """从**索引快照**恢复派生边(零读文件)——台账丢失/未重建时的只读兜底。"""
243
- nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
244
- out = []
245
- for nid, e in nodes.items():
246
- if prefix and not str(nid).startswith(prefix):
247
- continue
248
- rel = coerce_relation((e or {}).get(FM_REL_FIELD))
249
- for p in as_list((e or {}).get(FM_FIELD)):
250
- out.append({"schema": SCHEMA, "child": nid, "parent": p, "rel": rel,
251
- "batch": (e or {}).get("derived_batch"), "actor": None,
252
- "origin": "index"})
253
- return _dedupe(out)
254
-
255
-
256
- # 生效条件:给定 cg 即返回 _dedupe(load(cg.root, path=path) + index_edges(cg)),台账记录在前、按边去重。
257
- def all_edges(cg, *, path: str = None) -> list:
258
- """台账 ∪ 索引声明(台账优先,按边去重)。"""
259
- return _dedupe(load(cg.root, path=path) + index_edges(cg))
260
-
261
-
262
- # 生效条件:include_index 为真值时取 all_edges(cg, path=path)、为假时取 load(cg.root, path=path),对 child/parent 不在 cg 索引节点键集合中的边记为 dangling 并列出 missing;返回 ok=not dangling,dangling 仅取 int(limit)(默认 20)项,不写盘。
263
- def check(cg, *, path: str = None, limit: int = 20,
264
- include_index: bool = True) -> dict:
265
- """只读巡检:检出**悬空派生边**(端点不在索引内)。**不删边、不写盘**。
266
-
267
- `ok=False` 仅表示「有悬空」,不代表巡检失败;`checked=True` 恒成立。
268
- """
269
- known = set((getattr(cg, "index", None) or {}).get("nodes") or {})
270
- rows = all_edges(cg, path=path) if include_index else load(cg.root, path=path)
271
- dangling = []
272
- for e in rows:
273
- missing = []
274
- if e.get("child") not in known:
275
- missing.append("child")
276
- if e.get("parent") not in known:
277
- missing.append("parent")
278
- if missing:
279
- dangling.append({"child": e.get("child"), "parent": e.get("parent"),
280
- "rel": e.get("rel"), "missing": missing,
281
- "batch": e.get("batch"), "t": e.get("t"),
282
- "origin": e.get("origin") or "ledger"})
283
- dangling.sort(key=lambda r: (r.get("child") or "", r.get("parent") or ""))
284
- ledger = ledger_file(cg.root, path)
285
- return {"ok": not dangling, "checked": True, "root": cg.root,
286
- "ledger": ledger, "ledger_exists": os.path.exists(ledger),
287
- "edges": len(rows), "nodes": len(known),
288
- "dangling": dangling[:int(limit)], "dangling_count": len(dangling),
289
- "readonly": True,
290
- "note": "只读巡检:悬空边仅检出并报告,不自动删除(关系事实由人处置)"}
291
-
292
-
293
- # 生效条件:apply 为假值(默认 False)时只返回 dry_run 报表(written=0、sample=es[:5]);apply 为真值时把 index_edges(cg) 的边写入 ledger_file(cg.root, path) 并返回 written=len(es)。
294
- def rebuild_ledger(cg, *, apply: bool = False, path: str = None) -> dict:
295
- """按 frontmatter 重建台账——**只重放已声明的边,不发明任何边**。
296
-
297
- 历史节点未声明派生关系 → 重建结果为空,正合「历史不回填」。
298
- 默认 dry_run(只出报表)。
299
- """
300
- es = index_edges(cg)
301
- if not apply:
302
- return {"ok": True, "dry_run": True, "edges": len(es),
303
- "written": 0, "sample": es[:5],
304
- "note": "预演:未写盘;只重放 frontmatter 已声明的关系"}
305
- body = "".join(json.dumps(e, ensure_ascii=False, separators=(",", ":")) + "\n"
306
- for e in es)
307
- p = ledger_file(cg.root, path)
308
- atomic_write(p, body)
309
- return {"ok": True, "dry_run": False, "edges": len(es),
310
- "written": len(es), "ledger": p}
311
-
312
-
313
- # 生效条件:check(cg, path=path, limit=3) 成功时返回 edges/dangling/ledger/exists/sample(悬空边 child->parent);该调用抛任何异常时被 except Exception 吞掉并返回 {}。
314
- def summary(cg, *, path: str = None) -> dict:
315
- """轻量摘要(只读;失败不抛,避免拖垮 health_os / 常驻循环)。"""
316
- try:
317
- rep = check(cg, path=path, limit=3)
318
- return {"edges": rep["edges"], "dangling": rep["dangling_count"],
319
- "ledger": rep["ledger"], "exists": rep["ledger_exists"],
320
- "sample": [f"{r['child']}->{r['parent']}"
321
- for r in rep["dangling"]]}
322
- except Exception: # noqa: BLE001
323
- return {}
324
-
325
-
326
- # 生效条件:root 为真值时 ledger 字段取 ledger_file(root),否则取常量 LEDGER_NAME;其余自描述字段(SCHEMA、RELATIONS、DEFAULT_RELATION、FM_FIELD/FM_REL_FIELD 等)恒定返回。
327
- def catalog(root: str = None) -> dict:
328
- """自描述(供 MCP catalog / 人工核对)。"""
329
- return {
330
- "layer": "派生溯源(G8)",
331
- "question": "这个节点从哪来(演进血缘)",
332
- "ledger": ledger_file(root) if root else LEDGER_NAME,
333
- "schema": SCHEMA,
334
- "relations": list(RELATIONS),
335
- "default_relation": DEFAULT_RELATION,
336
- "fm_fields": [FM_FIELD, FM_REL_FIELD],
337
- "discipline": {"incremental_only": True, "no_backfill": True,
338
- "never_block_write": True, "patrol_readonly": True},
339
- "distinct_from": ("links.py = 跨节点信任 P_trust(_links.json);"
340
- "本层 = 节点派生关系(_link.jsonl)"),
341
- }
342
-
343
-
344
- # --------------------------------------------------------------------------
345
- # 读:三元组反查原语(阶段二 4.2 · find_entity_contexts 式)
346
- # --------------------------------------------------------------------------
347
- # 术语映射(**同一件事,勿新造第二套字段**):三元组 `subject / predicate /
348
- # object` 在本层就是派生边的 `child / rel / parent`——即节点写入时声明的
349
- # `derived_from` 关系。不另建 subject/predicate/object 参数字面量,理由有二:
350
- # ① `cg` 工具面是**扁平 schema**,`subject` 已被 `identity`(`subject:<id>`
351
- # 主体语义)与 `link.evidence`(证据主体)占用,同名异义会把两处口径搅在一起;
352
- # ② `edges()` 已是唯一谓词载体(child/parent/relation/batch 四键),反查只是它的
353
- # **只读超集**——另造一套参数必然分叉。
354
- # 与 `edges()` 的三处**有意**差异(不是漂移):
355
- # · 时间轴缺省 `observed`(边只有记录时刻 `t`,见 FIND_DEFAULT_AXIS);
356
- # · 默认排序 `desc`(按 `t` 新→旧)且 `limit` 缺省 50、上限 500(分页原语,
357
- # 不给「静默全量倾倒」);
358
- # · 加 `aggregation`(分页前全集分桶)与 `expand_nodes`(端点摘要,索引级零读盘)。
359
-
360
- #: 排序方向(封闭枚举,拒收未知名——与 `trust.TIME_OPERATORS` 同风格)
361
- ORDERINGS = ("desc", "asc")
362
- #: 聚合维度(封闭枚举):按谓词 / 对象端 / 主体端分桶
363
- AGGREGATIONS = ("by_relation", "by_parent", "by_child")
364
- #: 聚合维度 → 边字段(谓词在边上叫 `rel`;聚合名沿用三元组术语命名)
365
- _AGG_FIELD = {"by_relation": "rel", "by_parent": "parent", "by_child": "child"}
366
- #: 反查缺省时间轴:派生边只有一个时刻字段 `t`(写入时刻,观察轴),
367
- #: **效力轴字段根本不存在**。与 `mdcg.search` 缺省 `effective` **有意不同**:
368
- #: 那里 `effective_from/until` 是可选声明(多数节点没写),沿用 fail-open 不会
369
- #: 出错;这里若缺省 `effective`,则「给了时间条件却恒不过滤」——把静默 no-op
370
- #: 当成了「没有匹配」,属无法复算的错答。
371
- FIND_DEFAULT_AXIS = "observed"
372
- DEFAULT_FIND_LIMIT = 50
373
- MAX_FIND_LIMIT = 500
374
- #: `expand_nodes=True` 时透出的索引字段白名单(只读索引快照,**零读节点文件**)
375
- EXPAND_FIELDS = ("layer", "tags", "importance", "writer", "session",
376
- "derived_from", "derived_relation", "derived_batch",
377
- "temporal", "time_window", "condition_space")
378
-
379
-
380
- # 生效条件:value 为 None 或 str(value).strip() 为空时返回 "desc";小写后命中 ORDERINGS 返回该值;否则抛 ProvenanceError。
381
- def _ordering_of(value) -> str:
382
- """排序方向归一 → `"desc"` / `"asc"`;未知 → `ProvenanceError`(fail-closed)。"""
383
- if value is None or not str(value).strip():
384
- return "desc"
385
- v = str(value).strip().lower()
386
- if v not in ORDERINGS:
387
- raise ProvenanceError(f"未知 ordering {value!r}(允许:{ORDERINGS})")
388
- return v
389
-
390
-
391
- # 生效条件:value 为 None 或 str(value).strip() 为空时返回 None(= 不聚合);小写后命中 AGGREGATIONS 返回该值;否则抛 ProvenanceError。
392
- def _aggregation_of(value):
393
- """聚合维度归一 → `None` / `AGGREGATIONS` 之一;未知 → `ProvenanceError`。"""
394
- if value is None or not str(value).strip():
395
- return None
396
- v = str(value).strip().lower()
397
- if v not in AGGREGATIONS:
398
- raise ProvenanceError(f"未知 aggregation {value!r}(允许:{AGGREGATIONS})")
399
- return v
400
-
401
-
402
- # 生效条件:axis 为 "observed" 且 edge.get("t") 可经 trust.parse_time 解析时返回 (t, t, False);axis 非 observed 或 t 不可解析/缺失时返回 (None, None, True)。
403
- def _edge_window(edge, axis: str):
404
- """边的轴窗口 → `(start, end, missing)`(与 `trust.time_window_of` **同形**)。
405
-
406
- observed 轴:`t`(写入时刻)→ `(t, t, False)`;`t` 缺失(`index_edges` 兜底边
407
- 不带时间)→ `(None, None, True)`。其余轴一律 `(None, None, True)`——边没有效力轴
408
- 声明可读,如实报「不可判定」,由**轴策略**处置(observed fail-closed 剔除并计入
409
- `axis_missing`;effective fail-open 保留),不在这里悄悄换轴。
410
- """
411
- if str(axis) == "observed":
412
- t = _trust.parse_time((edge or {}).get("t"))
413
- if t is None:
414
- return None, None, True
415
- return t, t, False
416
- return None, None, True
417
-
418
-
419
- # 生效条件:start_operator 与 end_operator 均为 None 时返回 "overlap",否则返回 "endpoint"。
420
- def _mode_of(start_operator, end_operator) -> str:
421
- """时间过滤模式(与 `trust.window_match` 的显式分叉口径同源,不另立判据)。"""
422
- return "endpoint" if (start_operator is not None or end_operator is not None) \
423
- else "overlap"
424
-
425
-
426
- # 生效条件:nid 不在 (cg.index or {}).get("nodes") or {} 的 dict 条目中(含 cg.index 缺失、条目非 dict)时返回 {'id': nid, 'present': False};否则返回 {'id','present':True} 并附 EXPAND_FIELDS 中值非 None 的字段。
427
- def _node_digest(cg, nid) -> dict:
428
- """端点摘要(只读索引快照,**零读节点文件**);端点缺失 → `present=False`。"""
429
- nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
430
- e = nodes.get(nid)
431
- if not isinstance(e, dict):
432
- return {"id": nid, "present": False}
433
- d = {"id": nid, "present": True}
434
- for k in EXPAND_FIELDS:
435
- if e.get(k) is not None:
436
- d[k] = e[k]
437
- return d
438
-
439
-
440
- # 生效条件:child/parent/relation/batch 各为真值(str(x).strip() 非空)时归一为过滤条件,relation 经 normalize_relation 非法即抛 ProvenanceError;ordering/aggregation 经 _ordering_of/_aggregation_of 非法即抛;offset 为负或 limit<=0 或 limit>MAX_FIND_LIMIT 即抛;时间五参经 trust.check_time_args 校验,why 非空即抛 ProvenanceError;返回 {'ok': True, 'readonly': True, 'op': 'edges', 'triple', 'total', 'matched', 'returned', 'offset', 'limit', 'ordering', 'aggregation', 'edges', 'aggregates', 'nodes_expanded', 'time_filter', 'ledger', 'distinct_from', 'note'},其中 total=谓词过滤后条数、matched=时间过滤后条数(dropped+matched==total)、edges=按 t 排序后 offset:offset+limit 切片(expand_nodes 时每边附 child_node/parent_node 摘要)、aggregates 为分页前全集分桶(未请求为 None)。
441
- def find_edges(cg, *, child=None, parent=None, relation=None, batch=None,
442
- start_time=None, end_time=None, start_operator=None,
443
- end_operator=None, time_axis=None, ordering=None, offset=0,
444
- limit=None, aggregation=None, expand_nodes: bool = False,
445
- path: str = None) -> dict:
446
- """三元组反查(阶段二 4.2):按**任意端 / 谓词 / 时间**反查派生边(只读)。
447
-
448
- `subject/predicate/object` ≡ `child/rel/parent`(见本节术语映射注释)。
449
-
450
- 谓词(`child` / `parent` / `relation` / `batch`)与 `edges()` **同源同义**:
451
- 给了就等值过滤、不给就不过滤。时间条件走 `trust.check_time_args`
452
- (**与检索共用的唯一校验点**,本层不另写一套),比较语义由
453
- `trust.window_match` 提供(不给 operator = 区间重叠;给 operator = 端点比较)。
454
-
455
- fail-closed 清单(宁可报错,不静默降级):
456
- · `relation` 非 `RELATIONS`(经 `normalize_relation`);
457
- · `ordering` / `aggregation` 非各自枚举;
458
- · `offset < 0`;`limit <= 0` 或 `> MAX_FIND_LIMIT`(**不把 0/负数当「全部」**);
459
- · 时间五参非法(未知轴 / 未知算子 / 给了算子缺时间 / start > end)。
460
-
461
- 分页与聚合的次序是刻意的:**聚合基于分页前全集**(`matched`),
462
- 否则「先切页再聚合」会给出随 offset 漂移的分桶——不可复算。
463
- 审计块 `time_filter.dropped + matched == total` 由本函数保证。
464
- """
465
- # ---- 谓词归一(空/空白 = 不约束,与 edges() 同口径) ------------------
466
- c_f = str(child).strip() if child is not None and str(child).strip() else None
467
- p_f = str(parent).strip() if parent is not None and str(parent).strip() else None
468
- b_f = str(batch).strip() if batch is not None and str(batch).strip() else None
469
- r_f = normalize_relation(relation) if (relation is not None
470
- and str(relation).strip()) else None
471
- # ---- 排序 / 分页 / 聚合 入参校验 --------------------------------------
472
- ord_v = _ordering_of(ordering)
473
- agg_v = _aggregation_of(aggregation)
474
- try:
475
- off = int(offset or 0)
476
- except (TypeError, ValueError) as exc:
477
- raise ProvenanceError(f"offset 非法:{offset!r}") from exc
478
- if off < 0:
479
- raise ProvenanceError(f"offset 不能为负:{off}")
480
- lim = DEFAULT_FIND_LIMIT if limit is None else int(limit)
481
- if lim <= 0:
482
- raise ProvenanceError(f"limit 必须为正整数(0/负数不当「全部」):{limit!r}")
483
- if lim > MAX_FIND_LIMIT:
484
- raise ProvenanceError(f"limit 超上限 {MAX_FIND_LIMIT}:{lim}")
485
- # ---- 时间算子:复用唯一校验点,缺省轴按本层语义补 observed -----------
486
- enabled, axis0, why = _trust.check_time_args(
487
- start_time=start_time, end_time=end_time,
488
- start_operator=start_operator, end_operator=end_operator,
489
- time_axis=time_axis)
490
- if why:
491
- raise ProvenanceError(why)
492
- axis = FIND_DEFAULT_AXIS if time_axis is None else axis0
493
- q_s = _trust.parse_time(start_time) if enabled else None
494
- q_e = _trust.parse_time(end_time) if enabled else None
495
-
496
- rows = all_edges(cg, path=path)
497
- cand = []
498
- for e in rows:
499
- if c_f and e.get("child") != c_f:
500
- continue
501
- if p_f and e.get("parent") != p_f:
502
- continue
503
- if r_f and e.get("rel") != r_f:
504
- continue
505
- if b_f and e.get("batch") != b_f:
506
- continue
507
- cand.append(e)
508
- total = len(cand)
509
-
510
- # ---- 时间过滤(候选层:与 trust.filter_by_time 同策略) ---------------
511
- dropped = missing = 0
512
- kept = []
513
- for e in cand:
514
- if not enabled:
515
- kept.append(e)
516
- continue
517
- cs, ce, miss = _edge_window(e, axis)
518
- if miss:
519
- if axis == "observed": # 观察轴 fail-closed
520
- dropped += 1
521
- missing += 1
522
- continue
523
- kept.append(e) # 效力轴 fail-open(无效力声明可读)
524
- continue
525
- if _trust.window_match(cs, ce, q_s, q_e, start_operator, end_operator):
526
- kept.append(e)
527
- else:
528
- dropped += 1
529
- if dropped + len(kept) != total: # 审计不变式(可复算)
530
- raise ProvenanceError(
531
- f"审计不变式破缺:dropped({dropped}) + kept({len(kept)}) != total({total})")
532
-
533
- # ---- 排序(t 缺失按 0 计,确定性次级键防抖) --------------------------
534
- kept.sort(key=lambda e: (float(e.get("t") or 0.0),
535
- str(e.get("child") or ""),
536
- str(e.get("parent") or "")),
537
- reverse=(ord_v == "desc"))
538
- page = kept[off:off + lim]
539
- if expand_nodes:
540
- page = [dict(e) for e in page]
541
- for e in page:
542
- e["child_node"] = _node_digest(cg, e.get("child"))
543
- e["parent_node"] = _node_digest(cg, e.get("parent"))
544
-
545
- # ---- 聚合(分页前全集;无分页漂移) ----------------------------------
546
- aggregates = None
547
- if agg_v:
548
- field = _AGG_FIELD[agg_v]
549
- buckets = {}
550
- for e in kept:
551
- k = str(e.get(field) or "")
552
- b = buckets.get(k)
553
- if b is None:
554
- b = buckets[k] = {"key": k, "count": 0, "t_min": None,
555
- "t_max": None, "sample": []}
556
- b["count"] += 1
557
- t = e.get("t")
558
- if t is not None:
559
- t = float(t)
560
- b["t_min"] = t if b["t_min"] is None else min(b["t_min"], t)
561
- b["t_max"] = t if b["t_max"] is None else max(b["t_max"], t)
562
- if len(b["sample"]) < 3:
563
- b["sample"].append(f"{e.get('child')}->{e.get('parent')}"
564
- f"({e.get('rel')})")
565
- aggregates = sorted(buckets.values(), key=lambda b: (-b["count"], b["key"]))
566
-
567
- return {"ok": True, "readonly": True, "op": "edges",
568
- "triple": {"child": c_f, "relation": r_f, "parent": p_f, "batch": b_f},
569
- "total": total, "matched": len(kept), "returned": len(page),
570
- "offset": off, "limit": lim, "ordering": ord_v,
571
- "aggregation": agg_v, "aggregates": aggregates,
572
- "nodes_expanded": bool(expand_nodes),
573
- "edges": page,
574
- "time_filter": _trust.time_filter_meta(
575
- axis=axis, mode=_mode_of(start_operator, end_operator),
576
- start=start_time, end=end_time,
577
- start_operator=start_operator, end_operator=end_operator,
578
- dropped=dropped, axis_missing=missing, applied=bool(enabled)),
579
- "ledger": ledger_file(cg.root, path),
580
- "distinct_from": "links.py = 跨节点信任 P_trust(_links.json)",
581
- "note": ("只读:台账 ∪ 索引声明(台账优先,按边去重);"
582
- "聚合基于分页前全集;边时刻字段为 t(观察轴),"
1
+ # -*- coding: utf-8 -*-
2
+ """派生溯源(G8):新增节点常态化建链 + 悬空可检出。
3
+
4
+ 回答「这个节点**从哪来**」——与 `md_cg.links` 刻意分层:
5
+ · `links.py` = 跨节点信任(「我信你多少」,P_trust,落 `~/.mdcg/_links.json`);
6
+ · 本模块 = 节点派生关系(「它由谁派生」,落 `<root>/_link.jsonl`)。
7
+ 两者都叫「链接」,但一个管**信任状态**、一个管**演进血缘**,不可混用。
8
+
9
+ 存储形态(对齐「md 单一真相源 + 派生索引可重建」):
10
+ · 权威声明在节点 frontmatter(`derived_from` / `derived_relation`);
11
+ · `<root>/_link.jsonl` 是 **append-only 派生台账**(快查用,可由 frontmatter 重建);
12
+ · 建链失败写 `<root>/_link.jsonl.fail`(降级留痕)。
13
+
14
+ 三条纪律(对齐 G8 裁定 §六):
15
+ 1. **只对新增节点常态化建链,历史不回填**——`rebuild_ledger` 只重放 frontmatter
16
+ 里**已经声明**的关系,不为历史节点发明任何边(当前库历史声明为 0 → 重建为空);
17
+ 2. **建链失败不得阻断写入**——`record()` 永不抛(best-effort),失败降级为告警 +
18
+ 失败台账留痕,节点写入照常提交;
19
+ 3. **巡检只读**——`check()` 检出悬空边(目标/子节点不在索引内)但**不自动删边**,
20
+ 关系事实去留由人处置。
21
+
22
+ 零第三方依赖。
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import json
27
+ import os
28
+ import time
29
+
30
+ from . import trust as _trust
31
+ from .fsutil import FileLock, append_jsonl, atomic_write, read_jsonl
32
+
33
+ LEDGER_NAME = "_link.jsonl"
34
+ FAIL_SUFFIX = ".fail"
35
+ LEDGER_ENV = "MDCG_LINK_FILE"
36
+ SCHEMA = 1
37
+
38
+ #: 允许的派生关系(显式枚举,避免「自由字符串」把血缘写成噪声)
39
+ RELATIONS = ("derived_from", "split_from", "extracted_from",
40
+ "merged_from", "refined_from", "source")
41
+ DEFAULT_RELATION = "derived_from"
42
+
43
+ #: frontmatter 里承载派生声明的字段(写路径只读这两处,不猜)
44
+ FM_FIELD = "derived_from"
45
+ FM_REL_FIELD = "derived_relation"
46
+
47
+ _LOCK_TIMEOUT = 2.0
48
+
49
+
50
+ class ProvenanceError(Exception):
51
+ """派生溯源错误。写路径侧一律由 `record()` 兜住,不向上抛。"""
52
+
53
+
54
+ # --------------------------------------------------------------------------
55
+ # 路径 / 规范化
56
+ # --------------------------------------------------------------------------
57
+
58
+ # 生效条件:path 为真值时返回 path;否则 os.environ.get(LEDGER_ENV) 为非空真值时返回该环境变量值;否则返回 os.path.join(root, LEDGER_NAME)。
59
+ def ledger_file(root: str, path: str = None) -> str:
60
+ """台账路径:显式 → MDCG_LINK_FILE → <root>/_link.jsonl。"""
61
+ return path or os.environ.get(LEDGER_ENV) or os.path.join(root, LEDGER_NAME)
62
+
63
+
64
+ # 生效条件:传入 root(path 为真值则以其为准,否则由 ledger_file 的回落决定落点)时返回 ledger_file(root, path) 结果拼接 FAIL_SUFFIX。
65
+ def fail_log_file(root: str, path: str = None) -> str:
66
+ """降级留痕路径(台账写不进时的「本该建的边」)。"""
67
+ return ledger_file(root, path) + FAIL_SUFFIX
68
+
69
+
70
+ # 生效条件:value 为 None 返回 [];否则 list/tuple/set 逐个元素、其他类型视作单元素,元素经 str(x).strip() 后非空且未出现过才保留(重复只留首次)。
71
+ def as_list(value) -> list:
72
+ """把单值 / 序列统一成去重、去空白的字符串列表。"""
73
+ if value is None:
74
+ return []
75
+ items = list(value) if isinstance(value, (list, tuple, set)) else [value]
76
+ out = []
77
+ for x in items:
78
+ s = str(x).strip()
79
+ if s and s not in out:
80
+ out.append(s)
81
+ return out
82
+
83
+
84
+ # 生效条件:rel 为假值(None/空串)或 str(rel).strip().lower() 后为空白的串(如 " ")时 r 回落 default(默认常量 DEFAULT_RELATION);r 不在 RELATIONS 内(含回落后的 default 本身非法)即抛 ProvenanceError,否则返回该小写串。
85
+ def normalize_relation(rel, default: str = DEFAULT_RELATION) -> str:
86
+ """严格校验关系名;非法抛 `ProvenanceError`(显式 API 用)。"""
87
+ r = str(rel or "").strip().lower()
88
+ if not r:
89
+ r = default
90
+ if r not in RELATIONS:
91
+ raise ProvenanceError(f"未知派生关系:{rel}(允许:{RELATIONS})")
92
+ return r
93
+
94
+
95
+ # 生效条件:normalize_relation(rel, default) 抛 ProvenanceError(含 rel 为假值/仅空白且 default 非法时)则原样返回 default;否则返回该调用的返回值。
96
+ def coerce_relation(rel, default: str = DEFAULT_RELATION) -> str:
97
+ """宽松兜底:非法关系名回退默认值(**写路径用,保证永不阻断写入**)。"""
98
+ try:
99
+ return normalize_relation(rel, default)
100
+ except ProvenanceError:
101
+ return default
102
+
103
+
104
+ # 生效条件:child 或 parent 为假值或仅空白使 str(x or "").strip() 为空、或二者 strip 后相等(自环)时返回 None;否则返回含 schema/t/child/parent/rel/batch/actor 的 dict,note 为真值时才附上并截断到 200 字符。
105
+ def make_edge(child, parent, *, relation=DEFAULT_RELATION, batch=None,
106
+ actor="system", note=None, t=None):
107
+ """构造一条派生边;自环 / 空端点返回 None(**不产生无意义边**)。"""
108
+ c, p = str(child or "").strip(), str(parent or "").strip()
109
+ if not c or not p or c == p:
110
+ return None
111
+ edge = {"schema": SCHEMA,
112
+ "t": float(t if t is not None else time.time()),
113
+ "child": c, "parent": p, "rel": coerce_relation(relation),
114
+ "batch": batch, "actor": actor}
115
+ if note:
116
+ edge["note"] = str(note)[:200]
117
+ return edge
118
+
119
+
120
+ # 生效条件:对 as_list(parents) 的每个父项调用 make_edge,只保留返回非 None 的边,全部被丢弃时返回空列表。
121
+ def edges_for(child, parents, **kw) -> list:
122
+ """`(child, [parents]) → [edge]`:空端点 / 自环自动丢弃。"""
123
+ out = []
124
+ for p in as_list(parents):
125
+ e = make_edge(child, p, **kw)
126
+ if e:
127
+ out.append(e)
128
+ return out
129
+
130
+
131
+ # --------------------------------------------------------------------------
132
+ # 写:台账追加(record 为写路径唯一入口,永不抛)
133
+ # --------------------------------------------------------------------------
134
+
135
+ # 生效条件:edges 为 None/空/全为假元素时返回 0;否则在 FileLock(p, timeout=_LOCK_TIMEOUT) 内逐条 append_jsonl,抛 OSError 时转抛 ProvenanceError,成功返回 len(edges)。
136
+ def append(root: str, edges, *, path: str = None) -> int:
137
+ """台账追加(加锁串行,防 Windows 并发交错丢边)。IO 失败抛 `ProvenanceError`。"""
138
+ edges = [e for e in (edges or []) if e]
139
+ if not edges:
140
+ return 0
141
+ p = ledger_file(root, path)
142
+ try:
143
+ os.makedirs(os.path.dirname(os.path.abspath(p)) or ".", exist_ok=True)
144
+ with FileLock(p, timeout=_LOCK_TIMEOUT):
145
+ for e in edges:
146
+ append_jsonl(p, e)
147
+ except OSError as exc:
148
+ raise ProvenanceError(f"派生台账写入失败:{p}({exc})") from exc
149
+ return len(edges)
150
+
151
+
152
+ # 生效条件:给定 root/path/child/parents/relation/reason/code 即恒返回 {'ok': False, 'added': 0, 'edges': [], 'degraded': True, 'degrade_code': code, 'reason': reason},其中写 fail_log_file 台账的异常被 except Exception 吞掉。
153
+ def _degrade(root: str, path, child, parents, relation, reason,
154
+ code: str) -> dict:
155
+ """降级留痕:把「本该建的边」记进 .fail 台账(自身也 best-effort)。"""
156
+ rec = {"t": time.time(), "code": code, "child": str(child or ""),
157
+ "parents": as_list(parents), "rel": str(relation or ""),
158
+ "reason": str(reason)[:300]}
159
+ try:
160
+ append_jsonl(fail_log_file(root, path), rec)
161
+ except Exception: # noqa: BLE001
162
+ pass
163
+ return {"ok": False, "added": 0, "edges": [], "degraded": True,
164
+ "degrade_code": code, "reason": reason}
165
+
166
+
167
+ # 生效条件:edges_for 抛异常时经 _degrade(code='bad_edge') 返回;edges 为空时返回 {'ok': True, 'added': 0, 'edges': [], 'reason': 'no_parents'};append 抛 ProvenanceError 或其他异常时经 _degrade(code='ledger_io') 返回;其余返回 {'ok': True, 'added': n, 'edges': edges, 'ledger': …},恒不向外抛。
168
+ def record(root: str, child, parents, *, relation=DEFAULT_RELATION,
169
+ batch=None, actor="system", note=None, path: str = None) -> dict:
170
+ """写路径建链入口:**永不抛**(G8 硬约束:建链失败不得阻断节点写入)。
171
+
172
+ 返回 `{ok, added, edges, ...}`;失败时 `ok=False` + `degraded=True` 且已写
173
+ `.fail` 留痕。调用方**不得**因本函数返回 False 而回滚节点。
174
+ """
175
+ try:
176
+ edges = edges_for(child, parents, relation=relation, batch=batch,
177
+ actor=actor, note=note)
178
+ except Exception as exc: # noqa: BLE001
179
+ return _degrade(root, path, child, parents, relation,
180
+ f"{type(exc).__name__}: {exc}", "bad_edge")
181
+ if not edges:
182
+ return {"ok": True, "added": 0, "edges": [], "reason": "no_parents"}
183
+ try:
184
+ n = append(root, edges, path=path)
185
+ except ProvenanceError as exc:
186
+ return _degrade(root, path, child, parents, relation, str(exc),
187
+ "ledger_io")
188
+ except Exception as exc: # noqa: BLE001
189
+ return _degrade(root, path, child, parents, relation,
190
+ f"{type(exc).__name__}: {exc}", "ledger_io")
191
+ return {"ok": True, "added": n, "edges": edges,
192
+ "ledger": ledger_file(root, path)}
193
+
194
+
195
+ # --------------------------------------------------------------------------
196
+ # 读:台账 / 索引 / 悬空巡检
197
+ # --------------------------------------------------------------------------
198
+
199
+ # 生效条件:遍历 read_jsonl(ledger_file(root, path)),仅收录 isinstance(r, dict) 且 r.get("child") 与 r.get("parent") 均为真值(键缺失或值为假即丢弃)的记录。
200
+ def load(root: str, *, path: str = None) -> list:
201
+ """读台账(跳过坏行;只取有端点的记录)。"""
202
+ out = []
203
+ for r in read_jsonl(ledger_file(root, path)):
204
+ if isinstance(r, dict) and r.get("child") and r.get("parent"):
205
+ out.append(r)
206
+ return out
207
+
208
+
209
+ # 生效条件:rows 中每行按 (r.get("child"), r.get("parent"), r.get("rel")) 三元组判重,仅首次出现的行保留,按原顺序返回去重列表。
210
+ def _dedupe(rows):
211
+ out, seen = [], set()
212
+ for r in rows:
213
+ key = (r.get("child"), r.get("parent"), r.get("rel"))
214
+ if key in seen:
215
+ continue
216
+ seen.add(key)
217
+ out.append(r)
218
+ return out
219
+
220
+
221
+ # 生效条件:child/parent/relation/batch 各为真值时才做对应等值过滤(假值或 None 不过滤),去重后 limit 为真值时返回 out[:int(limit)],limit 为 None 或 0 时返回全部 out。
222
+ def edges(root: str, *, child=None, parent=None, relation=None, batch=None,
223
+ limit: int = None, path: str = None) -> list:
224
+ """按端点 / 关系 / 批次过滤台账边(只读,去重,保持写入顺序)。"""
225
+ out = []
226
+ for r in load(root, path=path):
227
+ if child and r.get("child") != child:
228
+ continue
229
+ if parent and r.get("parent") != parent:
230
+ continue
231
+ if relation and r.get("rel") != relation:
232
+ continue
233
+ if batch and r.get("batch") != batch:
234
+ continue
235
+ out.append(r)
236
+ out = _dedupe(out)
237
+ return out[:int(limit)] if limit else out
238
+
239
+
240
+ # 生效条件:以 (getattr(cg, "index", None) or {}).get("nodes") or {} 遍历(缺失时视作空);prefix 为真值时仅保留 str(nid).startswith(prefix) 的节点,输出经 _dedupe 去重。
241
+ def index_edges(cg, *, prefix: str = None) -> list:
242
+ """从**索引快照**恢复派生边(零读文件)——台账丢失/未重建时的只读兜底。"""
243
+ nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
244
+ out = []
245
+ for nid, e in nodes.items():
246
+ if prefix and not str(nid).startswith(prefix):
247
+ continue
248
+ rel = coerce_relation((e or {}).get(FM_REL_FIELD))
249
+ for p in as_list((e or {}).get(FM_FIELD)):
250
+ out.append({"schema": SCHEMA, "child": nid, "parent": p, "rel": rel,
251
+ "batch": (e or {}).get("derived_batch"), "actor": None,
252
+ "origin": "index"})
253
+ return _dedupe(out)
254
+
255
+
256
+ # 生效条件:给定 cg 即返回 _dedupe(load(cg.root, path=path) + index_edges(cg)),台账记录在前、按边去重。
257
+ def all_edges(cg, *, path: str = None) -> list:
258
+ """台账 ∪ 索引声明(台账优先,按边去重)。"""
259
+ return _dedupe(load(cg.root, path=path) + index_edges(cg))
260
+
261
+
262
+ # 生效条件:include_index 为真值时取 all_edges(cg, path=path)、为假时取 load(cg.root, path=path),对 child/parent 不在 cg 索引节点键集合中的边记为 dangling 并列出 missing;返回 ok=not dangling,dangling 仅取 int(limit)(默认 20)项,不写盘。
263
+ def check(cg, *, path: str = None, limit: int = 20,
264
+ include_index: bool = True) -> dict:
265
+ """只读巡检:检出**悬空派生边**(端点不在索引内)。**不删边、不写盘**。
266
+
267
+ `ok=False` 仅表示「有悬空」,不代表巡检失败;`checked=True` 恒成立。
268
+ """
269
+ known = set((getattr(cg, "index", None) or {}).get("nodes") or {})
270
+ rows = all_edges(cg, path=path) if include_index else load(cg.root, path=path)
271
+ dangling = []
272
+ for e in rows:
273
+ missing = []
274
+ if e.get("child") not in known:
275
+ missing.append("child")
276
+ if e.get("parent") not in known:
277
+ missing.append("parent")
278
+ if missing:
279
+ dangling.append({"child": e.get("child"), "parent": e.get("parent"),
280
+ "rel": e.get("rel"), "missing": missing,
281
+ "batch": e.get("batch"), "t": e.get("t"),
282
+ "origin": e.get("origin") or "ledger"})
283
+ dangling.sort(key=lambda r: (r.get("child") or "", r.get("parent") or ""))
284
+ ledger = ledger_file(cg.root, path)
285
+ return {"ok": not dangling, "checked": True, "root": cg.root,
286
+ "ledger": ledger, "ledger_exists": os.path.exists(ledger),
287
+ "edges": len(rows), "nodes": len(known),
288
+ "dangling": dangling[:int(limit)], "dangling_count": len(dangling),
289
+ "readonly": True,
290
+ "note": "只读巡检:悬空边仅检出并报告,不自动删除(关系事实由人处置)"}
291
+
292
+
293
+ # 生效条件:apply 为假值(默认 False)时只返回 dry_run 报表(written=0、sample=es[:5]);apply 为真值时把 index_edges(cg) 的边写入 ledger_file(cg.root, path) 并返回 written=len(es)。
294
+ def rebuild_ledger(cg, *, apply: bool = False, path: str = None) -> dict:
295
+ """按 frontmatter 重建台账——**只重放已声明的边,不发明任何边**。
296
+
297
+ 历史节点未声明派生关系 → 重建结果为空,正合「历史不回填」。
298
+ 默认 dry_run(只出报表)。
299
+ """
300
+ es = index_edges(cg)
301
+ if not apply:
302
+ return {"ok": True, "dry_run": True, "edges": len(es),
303
+ "written": 0, "sample": es[:5],
304
+ "note": "预演:未写盘;只重放 frontmatter 已声明的关系"}
305
+ body = "".join(json.dumps(e, ensure_ascii=False, separators=(",", ":")) + "\n"
306
+ for e in es)
307
+ p = ledger_file(cg.root, path)
308
+ atomic_write(p, body)
309
+ return {"ok": True, "dry_run": False, "edges": len(es),
310
+ "written": len(es), "ledger": p}
311
+
312
+
313
+ # 生效条件:check(cg, path=path, limit=3) 成功时返回 edges/dangling/ledger/exists/sample(悬空边 child->parent);该调用抛任何异常时被 except Exception 吞掉并返回 {}。
314
+ def summary(cg, *, path: str = None) -> dict:
315
+ """轻量摘要(只读;失败不抛,避免拖垮 health_os / 常驻循环)。"""
316
+ try:
317
+ rep = check(cg, path=path, limit=3)
318
+ return {"edges": rep["edges"], "dangling": rep["dangling_count"],
319
+ "ledger": rep["ledger"], "exists": rep["ledger_exists"],
320
+ "sample": [f"{r['child']}->{r['parent']}"
321
+ for r in rep["dangling"]]}
322
+ except Exception: # noqa: BLE001
323
+ return {}
324
+
325
+
326
+ # 生效条件:root 为真值时 ledger 字段取 ledger_file(root),否则取常量 LEDGER_NAME;其余自描述字段(SCHEMA、RELATIONS、DEFAULT_RELATION、FM_FIELD/FM_REL_FIELD 等)恒定返回。
327
+ def catalog(root: str = None) -> dict:
328
+ """自描述(供 MCP catalog / 人工核对)。"""
329
+ return {
330
+ "layer": "派生溯源(G8)",
331
+ "question": "这个节点从哪来(演进血缘)",
332
+ "ledger": ledger_file(root) if root else LEDGER_NAME,
333
+ "schema": SCHEMA,
334
+ "relations": list(RELATIONS),
335
+ "default_relation": DEFAULT_RELATION,
336
+ "fm_fields": [FM_FIELD, FM_REL_FIELD],
337
+ "discipline": {"incremental_only": True, "no_backfill": True,
338
+ "never_block_write": True, "patrol_readonly": True},
339
+ "distinct_from": ("links.py = 跨节点信任 P_trust(_links.json);"
340
+ "本层 = 节点派生关系(_link.jsonl)"),
341
+ }
342
+
343
+
344
+ # --------------------------------------------------------------------------
345
+ # 读:三元组反查原语(阶段二 4.2 · find_entity_contexts 式)
346
+ # --------------------------------------------------------------------------
347
+ # 术语映射(**同一件事,勿新造第二套字段**):三元组 `subject / predicate /
348
+ # object` 在本层就是派生边的 `child / rel / parent`——即节点写入时声明的
349
+ # `derived_from` 关系。不另建 subject/predicate/object 参数字面量,理由有二:
350
+ # ① `cg` 工具面是**扁平 schema**,`subject` 已被 `identity`(`subject:<id>`
351
+ # 主体语义)与 `link.evidence`(证据主体)占用,同名异义会把两处口径搅在一起;
352
+ # ② `edges()` 已是唯一谓词载体(child/parent/relation/batch 四键),反查只是它的
353
+ # **只读超集**——另造一套参数必然分叉。
354
+ # 与 `edges()` 的三处**有意**差异(不是漂移):
355
+ # · 时间轴缺省 `observed`(边只有记录时刻 `t`,见 FIND_DEFAULT_AXIS);
356
+ # · 默认排序 `desc`(按 `t` 新→旧)且 `limit` 缺省 50、上限 500(分页原语,
357
+ # 不给「静默全量倾倒」);
358
+ # · 加 `aggregation`(分页前全集分桶)与 `expand_nodes`(端点摘要,索引级零读盘)。
359
+
360
+ #: 排序方向(封闭枚举,拒收未知名——与 `trust.TIME_OPERATORS` 同风格)
361
+ ORDERINGS = ("desc", "asc")
362
+ #: 聚合维度(封闭枚举):按谓词 / 对象端 / 主体端分桶
363
+ AGGREGATIONS = ("by_relation", "by_parent", "by_child")
364
+ #: 聚合维度 → 边字段(谓词在边上叫 `rel`;聚合名沿用三元组术语命名)
365
+ _AGG_FIELD = {"by_relation": "rel", "by_parent": "parent", "by_child": "child"}
366
+ #: 反查缺省时间轴:派生边只有一个时刻字段 `t`(写入时刻,观察轴),
367
+ #: **效力轴字段根本不存在**。与 `mdcg.search` 缺省 `effective` **有意不同**:
368
+ #: 那里 `effective_from/until` 是可选声明(多数节点没写),沿用 fail-open 不会
369
+ #: 出错;这里若缺省 `effective`,则「给了时间条件却恒不过滤」——把静默 no-op
370
+ #: 当成了「没有匹配」,属无法复算的错答。
371
+ FIND_DEFAULT_AXIS = "observed"
372
+ DEFAULT_FIND_LIMIT = 50
373
+ MAX_FIND_LIMIT = 500
374
+ #: `expand_nodes=True` 时透出的索引字段白名单(只读索引快照,**零读节点文件**)
375
+ EXPAND_FIELDS = ("layer", "tags", "importance", "writer", "session",
376
+ "derived_from", "derived_relation", "derived_batch",
377
+ "temporal", "time_window", "condition_space")
378
+
379
+
380
+ # 生效条件:value 为 None 或 str(value).strip() 为空时返回 "desc";小写后命中 ORDERINGS 返回该值;否则抛 ProvenanceError。
381
+ def _ordering_of(value) -> str:
382
+ """排序方向归一 → `"desc"` / `"asc"`;未知 → `ProvenanceError`(fail-closed)。"""
383
+ if value is None or not str(value).strip():
384
+ return "desc"
385
+ v = str(value).strip().lower()
386
+ if v not in ORDERINGS:
387
+ raise ProvenanceError(f"未知 ordering {value!r}(允许:{ORDERINGS})")
388
+ return v
389
+
390
+
391
+ # 生效条件:value 为 None 或 str(value).strip() 为空时返回 None(= 不聚合);小写后命中 AGGREGATIONS 返回该值;否则抛 ProvenanceError。
392
+ def _aggregation_of(value):
393
+ """聚合维度归一 → `None` / `AGGREGATIONS` 之一;未知 → `ProvenanceError`。"""
394
+ if value is None or not str(value).strip():
395
+ return None
396
+ v = str(value).strip().lower()
397
+ if v not in AGGREGATIONS:
398
+ raise ProvenanceError(f"未知 aggregation {value!r}(允许:{AGGREGATIONS})")
399
+ return v
400
+
401
+
402
+ # 生效条件:axis 为 "observed" 且 edge.get("t") 可经 trust.parse_time 解析时返回 (t, t, False);axis 非 observed 或 t 不可解析/缺失时返回 (None, None, True)。
403
+ def _edge_window(edge, axis: str):
404
+ """边的轴窗口 → `(start, end, missing)`(与 `trust.time_window_of` **同形**)。
405
+
406
+ observed 轴:`t`(写入时刻)→ `(t, t, False)`;`t` 缺失(`index_edges` 兜底边
407
+ 不带时间)→ `(None, None, True)`。其余轴一律 `(None, None, True)`——边没有效力轴
408
+ 声明可读,如实报「不可判定」,由**轴策略**处置(observed fail-closed 剔除并计入
409
+ `axis_missing`;effective fail-open 保留),不在这里悄悄换轴。
410
+ """
411
+ if str(axis) == "observed":
412
+ t = _trust.parse_time((edge or {}).get("t"))
413
+ if t is None:
414
+ return None, None, True
415
+ return t, t, False
416
+ return None, None, True
417
+
418
+
419
+ # 生效条件:start_operator 与 end_operator 均为 None 时返回 "overlap",否则返回 "endpoint"。
420
+ def _mode_of(start_operator, end_operator) -> str:
421
+ """时间过滤模式(与 `trust.window_match` 的显式分叉口径同源,不另立判据)。"""
422
+ return "endpoint" if (start_operator is not None or end_operator is not None) \
423
+ else "overlap"
424
+
425
+
426
+ # 生效条件:nid 不在 (cg.index or {}).get("nodes") or {} 的 dict 条目中(含 cg.index 缺失、条目非 dict)时返回 {'id': nid, 'present': False};否则返回 {'id','present':True} 并附 EXPAND_FIELDS 中值非 None 的字段。
427
+ def _node_digest(cg, nid) -> dict:
428
+ """端点摘要(只读索引快照,**零读节点文件**);端点缺失 → `present=False`。"""
429
+ nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
430
+ e = nodes.get(nid)
431
+ if not isinstance(e, dict):
432
+ return {"id": nid, "present": False}
433
+ d = {"id": nid, "present": True}
434
+ for k in EXPAND_FIELDS:
435
+ if e.get(k) is not None:
436
+ d[k] = e[k]
437
+ return d
438
+
439
+
440
+ # 生效条件:child/parent/relation/batch 各为真值(str(x).strip() 非空)时归一为过滤条件,relation 经 normalize_relation 非法即抛 ProvenanceError;ordering/aggregation 经 _ordering_of/_aggregation_of 非法即抛;offset 为负或 limit<=0 或 limit>MAX_FIND_LIMIT 即抛;时间五参经 trust.check_time_args 校验,why 非空即抛 ProvenanceError;返回 {'ok': True, 'readonly': True, 'op': 'edges', 'triple', 'total', 'matched', 'returned', 'offset', 'limit', 'ordering', 'aggregation', 'edges', 'aggregates', 'nodes_expanded', 'time_filter', 'ledger', 'distinct_from', 'note'},其中 total=谓词过滤后条数、matched=时间过滤后条数(dropped+matched==total)、edges=按 t 排序后 offset:offset+limit 切片(expand_nodes 时每边附 child_node/parent_node 摘要)、aggregates 为分页前全集分桶(未请求为 None)。
441
+ def find_edges(cg, *, child=None, parent=None, relation=None, batch=None,
442
+ start_time=None, end_time=None, start_operator=None,
443
+ end_operator=None, time_axis=None, ordering=None, offset=0,
444
+ limit=None, aggregation=None, expand_nodes: bool = False,
445
+ path: str = None) -> dict:
446
+ """三元组反查(阶段二 4.2):按**任意端 / 谓词 / 时间**反查派生边(只读)。
447
+
448
+ `subject/predicate/object` ≡ `child/rel/parent`(见本节术语映射注释)。
449
+
450
+ 谓词(`child` / `parent` / `relation` / `batch`)与 `edges()` **同源同义**:
451
+ 给了就等值过滤、不给就不过滤。时间条件走 `trust.check_time_args`
452
+ (**与检索共用的唯一校验点**,本层不另写一套),比较语义由
453
+ `trust.window_match` 提供(不给 operator = 区间重叠;给 operator = 端点比较)。
454
+
455
+ fail-closed 清单(宁可报错,不静默降级):
456
+ · `relation` 非 `RELATIONS`(经 `normalize_relation`);
457
+ · `ordering` / `aggregation` 非各自枚举;
458
+ · `offset < 0`;`limit <= 0` 或 `> MAX_FIND_LIMIT`(**不把 0/负数当「全部」**);
459
+ · 时间五参非法(未知轴 / 未知算子 / 给了算子缺时间 / start > end)。
460
+
461
+ 分页与聚合的次序是刻意的:**聚合基于分页前全集**(`matched`),
462
+ 否则「先切页再聚合」会给出随 offset 漂移的分桶——不可复算。
463
+ 审计块 `time_filter.dropped + matched == total` 由本函数保证。
464
+ """
465
+ # ---- 谓词归一(空/空白 = 不约束,与 edges() 同口径) ------------------
466
+ c_f = str(child).strip() if child is not None and str(child).strip() else None
467
+ p_f = str(parent).strip() if parent is not None and str(parent).strip() else None
468
+ b_f = str(batch).strip() if batch is not None and str(batch).strip() else None
469
+ r_f = normalize_relation(relation) if (relation is not None
470
+ and str(relation).strip()) else None
471
+ # ---- 排序 / 分页 / 聚合 入参校验 --------------------------------------
472
+ ord_v = _ordering_of(ordering)
473
+ agg_v = _aggregation_of(aggregation)
474
+ try:
475
+ off = int(offset or 0)
476
+ except (TypeError, ValueError) as exc:
477
+ raise ProvenanceError(f"offset 非法:{offset!r}") from exc
478
+ if off < 0:
479
+ raise ProvenanceError(f"offset 不能为负:{off}")
480
+ lim = DEFAULT_FIND_LIMIT if limit is None else int(limit)
481
+ if lim <= 0:
482
+ raise ProvenanceError(f"limit 必须为正整数(0/负数不当「全部」):{limit!r}")
483
+ if lim > MAX_FIND_LIMIT:
484
+ raise ProvenanceError(f"limit 超上限 {MAX_FIND_LIMIT}:{lim}")
485
+ # ---- 时间算子:复用唯一校验点,缺省轴按本层语义补 observed -----------
486
+ enabled, axis0, why = _trust.check_time_args(
487
+ start_time=start_time, end_time=end_time,
488
+ start_operator=start_operator, end_operator=end_operator,
489
+ time_axis=time_axis)
490
+ if why:
491
+ raise ProvenanceError(why)
492
+ axis = FIND_DEFAULT_AXIS if time_axis is None else axis0
493
+ q_s = _trust.parse_time(start_time) if enabled else None
494
+ q_e = _trust.parse_time(end_time) if enabled else None
495
+
496
+ rows = all_edges(cg, path=path)
497
+ cand = []
498
+ for e in rows:
499
+ if c_f and e.get("child") != c_f:
500
+ continue
501
+ if p_f and e.get("parent") != p_f:
502
+ continue
503
+ if r_f and e.get("rel") != r_f:
504
+ continue
505
+ if b_f and e.get("batch") != b_f:
506
+ continue
507
+ cand.append(e)
508
+ total = len(cand)
509
+
510
+ # ---- 时间过滤(候选层:与 trust.filter_by_time 同策略) ---------------
511
+ dropped = missing = 0
512
+ kept = []
513
+ for e in cand:
514
+ if not enabled:
515
+ kept.append(e)
516
+ continue
517
+ cs, ce, miss = _edge_window(e, axis)
518
+ if miss:
519
+ if axis == "observed": # 观察轴 fail-closed
520
+ dropped += 1
521
+ missing += 1
522
+ continue
523
+ kept.append(e) # 效力轴 fail-open(无效力声明可读)
524
+ continue
525
+ if _trust.window_match(cs, ce, q_s, q_e, start_operator, end_operator):
526
+ kept.append(e)
527
+ else:
528
+ dropped += 1
529
+ if dropped + len(kept) != total: # 审计不变式(可复算)
530
+ raise ProvenanceError(
531
+ f"审计不变式破缺:dropped({dropped}) + kept({len(kept)}) != total({total})")
532
+
533
+ # ---- 排序(t 缺失按 0 计,确定性次级键防抖) --------------------------
534
+ kept.sort(key=lambda e: (float(e.get("t") or 0.0),
535
+ str(e.get("child") or ""),
536
+ str(e.get("parent") or "")),
537
+ reverse=(ord_v == "desc"))
538
+ page = kept[off:off + lim]
539
+ if expand_nodes:
540
+ page = [dict(e) for e in page]
541
+ for e in page:
542
+ e["child_node"] = _node_digest(cg, e.get("child"))
543
+ e["parent_node"] = _node_digest(cg, e.get("parent"))
544
+
545
+ # ---- 聚合(分页前全集;无分页漂移) ----------------------------------
546
+ aggregates = None
547
+ if agg_v:
548
+ field = _AGG_FIELD[agg_v]
549
+ buckets = {}
550
+ for e in kept:
551
+ k = str(e.get(field) or "")
552
+ b = buckets.get(k)
553
+ if b is None:
554
+ b = buckets[k] = {"key": k, "count": 0, "t_min": None,
555
+ "t_max": None, "sample": []}
556
+ b["count"] += 1
557
+ t = e.get("t")
558
+ if t is not None:
559
+ t = float(t)
560
+ b["t_min"] = t if b["t_min"] is None else min(b["t_min"], t)
561
+ b["t_max"] = t if b["t_max"] is None else max(b["t_max"], t)
562
+ if len(b["sample"]) < 3:
563
+ b["sample"].append(f"{e.get('child')}->{e.get('parent')}"
564
+ f"({e.get('rel')})")
565
+ aggregates = sorted(buckets.values(), key=lambda b: (-b["count"], b["key"]))
566
+
567
+ return {"ok": True, "readonly": True, "op": "edges",
568
+ "triple": {"child": c_f, "relation": r_f, "parent": p_f, "batch": b_f},
569
+ "total": total, "matched": len(kept), "returned": len(page),
570
+ "offset": off, "limit": lim, "ordering": ord_v,
571
+ "aggregation": agg_v, "aggregates": aggregates,
572
+ "nodes_expanded": bool(expand_nodes),
573
+ "edges": page,
574
+ "time_filter": _trust.time_filter_meta(
575
+ axis=axis, mode=_mode_of(start_operator, end_operator),
576
+ start=start_time, end=end_time,
577
+ start_operator=start_operator, end_operator=end_operator,
578
+ dropped=dropped, axis_missing=missing, applied=bool(enabled)),
579
+ "ledger": ledger_file(cg.root, path),
580
+ "distinct_from": "links.py = 跨节点信任 P_trust(_links.json)",
581
+ "note": ("只读:台账 ∪ 索引声明(台账优先,按边去重);"
582
+ "聚合基于分页前全集;边时刻字段为 t(观察轴),"
583
583
  "无效力轴字段——真实时间条件请用缺省 time_axis=observed")}