@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,667 +1,668 @@
1
- # -*- coding: utf-8 -*-
2
- """G5 · 视觉证据回填(守卫式 / 零 LLM / 不读图像 / 不重跑视觉)。
3
-
4
- 裁定依据
5
- --------
6
- `docs/mdcg/认知图_G4-G8缺口裁定单_v0.1.md` §三(四态 = ACCEPT,条件 = 脱敏 + 只读 AEIS)。
7
- 缺口根因(库外只读观测):产出侧 `vision_pipeline.cg_ingest` 只取
8
- `p.get("model_evidence")`,白箱 `geometry_parts` 部件不带该键 → 部件节点证据面
9
- 恒为 `{}`。即「证据口径未定义」,不是「没有证据」。
10
-
11
- 证据源(**主证据源**,只读、不落图、不落敏感语义文本)
12
- ------------------------------------------------------
13
- · `AEIS/data/vision/<图集>/parts_*.json`(如 `parts_0.json`)
14
- · `AEIS/data/vision/<图集>/vision_*.json`(同 schema:parts 带全部判定字段)
15
- · 权威口径文档:`AEIS/data/vision/VISION_PIPELINE_已验证_v1.md`
16
- 关键:**库内节点正文本身**也是同一白箱管线的持久化产物(部件行含
17
- `bbox / cond_hash / verdict / reason / fg`),可与归档逐部件结构化结果互证。
18
-
19
- 三档映射(裁定单 §三,逐字段必得有源,缺一不落)
20
- ------------------------------------------------
21
- · tier 1 模型证据:源数据含 `model_evidence` → `{model, kpts_used, min_conf}`。
22
- 本库归档与节点均无该键 → 本轮恒不适用。
23
- · tier 2 白箱证据:无模型键但判定要素齐 → 落
24
- `{algo, confidence, cond_hash, fg_ratio, occluded, verdict_reason}`。
25
- · tier 3 盲区:两者皆无 → `evidence` 保持为空,标 `evidence_status=BLINDSPOT`
26
- (附 `evidence_blindspot_reason`),**不编造**。
27
-
28
- 归因纪律(白箱优先、模型次之、缺失不编造)
29
- ------------------------------------------
30
- · 节点正文已记录者优先取节点实测(`cond_hash / fg_ratio / verdict_reason`);
31
- 节点未记录者(`algo / confidence / occluded`)由归档补全。
32
- · 连接键可验证:imgpart 家族用 `cond_hash` 精确连接;vpipe 家族用
33
- (家族根 `cond_hash` → 归档 `identity.cond_hash`)+ `type` 连接。
34
- · 连接后必须校验 `verdict` 一致;不一致 → 判 BLINDSPOT(`verdict_mismatch`),
35
- 不落半可信证据。
36
- · 6 字段任一取不到源 → BLINDSPOT(`missing_field:<name>`)。
37
- · 根节点(`部件树根`)不承载四态裁定 → 恒 BLINDSPOT(`root_no_verdict`)。
38
-
39
- 脱敏
40
- ----
41
- · 不读取任何图像文件;不重跑视觉;证据内只落结构化字段。
42
- · 图集目录名不进库:一律以编号引用(`图集_0`…`图集_9`,取自目录尾部 `_<N>`)。
43
- · `verdict_reason` 为算法产出的结构化判定理由(非敏感语义文本),且节点正文
44
- 原本已含该字段,故不构成新增泄露面。
45
-
46
- 纪律(对齐 backfill / consolidate)
47
- -----------------------------------
48
- · 不猜测:字段只在有源时写,来源写进 `evidence_source` / `evidence_joined_by`。
49
- · 可预演:`plan()` 只出报表;`apply()` 才写。
50
- · 可留痕:每次写入记一条 `_vision_evidence.jsonl`(批次 / 节点 / 档位 / 来源 /
51
- 连接键 / 写入字段)。
52
- · 可回滚:`rollback()` 按留痕反向删除本批次写入的证据键(幂等,防覆盖)。
53
- · fail-closed:密文节点一律跳过,绝不解密回写。
54
- · 只落 frontmatter 证据面:不动正文(含正文内联 `evidence={}` 槽),
55
- 避免改 `content_hash` 破坏上游去重——列为未闭合项。
56
- """
57
- from __future__ import annotations
58
-
59
- import glob
60
- import json
61
- import os
62
- import re
63
- import time
64
-
65
- from . import crypto, evolution
66
- from .fsutil import append_jsonl, read_jsonl
67
- from .mdcos import MdCGOS
68
-
69
- # ---- 常量 -----------------------------------------------------------------
70
-
71
- EVIDENCE_LOG = "_vision_evidence.jsonl"
72
-
73
- #: 视觉节点 id 前缀(G4 归位后位于 contextual 层)
74
- VISION_PREFIXES = ("imgpart_", "vpipe_")
75
-
76
- #: 视觉证据归档根(**本仓** data/vision,随大脑自带;只读)。
77
- #: 可用环境变量覆盖;`MDCG_AEIS_ROOT` 为三层拆分前的遗留名,仍兼容。
78
- VISION_ROOT_ENV = "MDCG_VISION_ROOT"
79
- LEGACY_VISION_ROOT_ENV = "MDCG_AEIS_ROOT"
80
- _HERE = os.path.dirname(os.path.abspath(__file__))
81
- #: 默认 = 本仓根 → 证据落在 <repo>/data/vision(拆分子项 B7:不再指向外部 AEIS 仓)
82
- DEFAULT_VISION_ROOT = os.path.dirname(_HERE)
83
-
84
- #: 权威口径文档(只读引用,写进报表供核对;相对证据归档根)
85
- AUTHORITY_DOC = "data/vision/VISION_PIPELINE_已验证_v1.md"
86
-
87
- #: 裁定单 §三 tier-2 六字段:逐字段必得有源,缺一即不落(→ BLINDSPOT)
88
- TIER2_FIELDS = ("algo", "confidence", "cond_hash", "fg_ratio", "occluded",
89
- "verdict_reason")
90
-
91
- STATUS_MODEL = "MODEL"
92
- STATUS_WHITEBOX = "WHITEBOX"
93
- STATUS_BLINDSPOT = "BLINDSPOT"
94
-
95
- #: 本轮写入的全部 frontmatter 证据键(回滚据此删除)
96
- EVIDENCE_KEYS = ("evidence", "evidence_status", "evidence_tier",
97
- "evidence_source", "evidence_joined_by", "evidence_doc",
98
- "evidence_blindspot_reason", "evidence_batch", "evidence_at")
99
-
100
- BATCH_DEFAULT = "visevid"
101
-
102
- ROOT_MARK = "部件树根"
103
-
104
- # 部件行:`<head> 部件 <type>: bbox=[..] <rest>`
105
- _RE_PART = re.compile(
106
- r"^(?P<head>.+?)\s+部件\s+(?P<type>[^::]+)\s*[::]\s*"
107
- r"bbox=\[(?P<bbox>[^\]]*)\]\s*(?P<rest>.*)$")
108
- _RE_COND = re.compile(r"cond_hash=([0-9a-fA-F]{6,})")
109
- _RE_VERDICT = re.compile(r"(?:^|\s)verdict=([A-Za-z]+)")
110
- _RE_REASON = re.compile(r"(?:^|\s)reason=(.*?)(?:\s+fg=|\s+evidence=|$)")
111
- _RE_FG = re.compile(r"(?:^|\s)fg=([0-9]*\.?[0-9]+)")
112
- _RE_NPARTS = re.compile(r"(\d+)\s*部件")
113
- _RE_IMG_TAG = re.compile(r"^img(\d+)$")
114
- _RE_DIR_N = re.compile(r"_(\d+)$")
115
-
116
-
117
- # ---- 通用工具 -------------------------------------------------------------
118
-
119
- # 生效条件:x 为 str 时返回 MdCGOS(x) 新实例,否则原样返回 x。
120
- def _as_cg(x):
121
- """接受 root 路径或已构造 cg 实例——保持密级隔离与密钥上下文。"""
122
- return MdCGOS(x) if isinstance(x, str) else x
123
-
124
-
125
- # 生效条件:无必需形参,调用即返回 time.strftime("%Y%m%d-%H%M%S") 的当前批次串。
126
- def _now_batch() -> str:
127
- return time.strftime("%Y%m%d-%H%M%S")
128
-
129
-
130
- # 生效条件:base 为字符串批号,先读 _log_path(cg) 的 jsonl 收集 batch 字段中以 base 开头的已有值,base 未被占用则原样返回 base,已占用则返回首个未占用的 f"{base}.{i}"(i 从 2 递增)。
131
- def _unique_batch(cg, base: str) -> str:
132
- """同秒重复调用时批号去重(后缀 .2/.3…),保证按批次回滚不打偏。"""
133
- seen = set()
134
- for rec in read_jsonl(_log_path(cg)) or []:
135
- b = rec.get("batch")
136
- if isinstance(b, str) and b.startswith(base):
137
- seen.add(b)
138
- if base not in seen:
139
- return base
140
- i = 2
141
- while f"{base}.{i}" in seen:
142
- i += 1
143
- return f"{base}.{i}"
144
-
145
-
146
- # 生效条件:cg 具 root 属性时取 cg.root、否则取 str(cg) 作为 root,返回 os.path.join(root, EVIDENCE_LOG)。
147
- def _log_path(cg) -> str:
148
- root = cg.root if hasattr(cg, "root") else str(cg)
149
- return os.path.join(root, EVIDENCE_LOG)
150
-
151
-
152
- # 生效条件:batch 与 nid 恒以 "%s|%s" 拼接成条目号,不做空值或类型校验。
153
- def _entry_id(batch: str, nid: str) -> str:
154
- return "%s|%s" % (batch, nid)
155
-
156
-
157
- # 生效条件:v 为 list/tuple 时返回各元素 str(x).strip() 后非空项以「;」连接;否则 v 为 None 返回空串,其余值返回 str(v).strip()。
158
- def _as_text(v) -> str:
159
- if isinstance(v, (list, tuple)):
160
- return ";".join(str(x).strip() for x in v if str(x).strip())
161
- return "" if v is None else str(v).strip()
162
-
163
-
164
- # 生效条件:path 经 abspath→dirname→basename 取名后匹配 _RE_DIR_N,命中则返回 int(m.group(1)),未命中返回 None。
165
- def _gallery_no(path: str):
166
- """图集编号:目录名尾部 `_<N>`;缺省 None(脱敏引用用)。"""
167
- name = os.path.basename(os.path.dirname(os.path.abspath(path)))
168
- m = _RE_DIR_N.search(name)
169
- return int(m.group(1)) if m else None
170
-
171
-
172
- # 生效条件:gal 非 None 时返回 "图集_%s" % gal,gal 为 None 时返回 "图集_?"。
173
- def _gallery_ref(gal) -> str:
174
- return "图集_%s" % (gal if gal is not None else "?")
175
-
176
-
177
- # 生效条件:无必需形参,按 os.environ.get(VISION_ROOT_ENV) or os.environ.get(LEGACY_VISION_ROOT_ENV) or DEFAULT_VISION_ROOT 取值——某环境变量为空串时视为假值继续回落下一项。
178
- def vision_root() -> str:
179
- """视觉证据归档根:env 覆盖 > 遗留 env > 本仓 data/vision 的父目录。"""
180
- return (os.environ.get(VISION_ROOT_ENV)
181
- or os.environ.get(LEGACY_VISION_ROOT_ENV)
182
- or DEFAULT_VISION_ROOT)
183
-
184
-
185
- #: 遗留别名(拆分前命名);新代码请用 vision_root()。
186
- aeis_root = vision_root
187
-
188
-
189
- # ---- 证据源(只读归档) ---------------------------------------------------
190
-
191
- # 生效条件:root 下 data/vision/*/*.json 逐文件读;文件 OSError/ValueError、JSON 顶层非 dict、parts 非非空 list、或过滤后(type 与 cond_hash 皆真值的 dict)无记录时跳过该文件,否则收入含 path/gallery/image_id/algo/identity_cond_hash/by_type/by_cond 的 src 并最终返回 out 列表。
192
- def load_sources(root: str) -> list:
193
- """扫描 `AEIS/data/vision/*/*.json`,取逐部件结构化结果(主证据源)。"""
194
- base = os.path.join(root, "data", "vision")
195
- out = []
196
- for p in sorted(glob.glob(os.path.join(base, "*", "*.json"))):
197
- try:
198
- with open(p, encoding="utf-8") as f:
199
- d = json.load(f)
200
- except (OSError, ValueError):
201
- continue
202
- if not isinstance(d, dict):
203
- continue
204
- parts = d.get("parts")
205
- if not isinstance(parts, list) or not parts:
206
- continue
207
- recs = [x for x in parts
208
- if isinstance(x, dict) and x.get("type") and x.get("cond_hash")]
209
- if not recs:
210
- continue
211
- ident = d.get("identity") if isinstance(d.get("identity"), dict) else {}
212
- src = {
213
- "path": p, "gallery": _gallery_no(p),
214
- "image_id": "" if d.get("image_id") is None else str(d.get("image_id")),
215
- "algo": d.get("algo"),
216
- "identity_cond_hash": ident.get("cond_hash"),
217
- "by_type": {}, "by_cond": {},
218
- }
219
- for r in recs:
220
- src["by_type"].setdefault(str(r["type"]), r)
221
- src["by_cond"].setdefault(str(r["cond_hash"]), []).append(r)
222
- out.append(src)
223
- return out
224
-
225
-
226
- # ---- 节点正文解析 ---------------------------------------------------------
227
-
228
- # 生效条件:content 为 None 或空串时按 "" 处理;逐行 strip 后跳过空行与以 # 开头的行,返回首个含 ROOT_MARK 或匹配 _RE_PART 的行,全部无命中返回 ""。
229
- def _find_body_line(content: str) -> str:
230
- for ln in (content or "").split("\n"):
231
- s = ln.strip()
232
- if not s or s.startswith("#"):
233
- continue
234
- if ROOT_MARK in s or _RE_PART.match(s):
235
- return s
236
- return ""
237
-
238
-
239
- # 生效条件:rest 为 None/假值时按 "" 处理,分别用 _RE_COND/_RE_VERDICT/_RE_REASON/_RE_FG 捕获;fg 命中则转 float、ValueError 时置 None;reason 命中并 strip 后为空则置 None;返回含 cond_hash/verdict/reason/fg_ratio 四键的 dict(未命中键值为 None)。
240
- def _fields(rest: str) -> dict:
241
- m = _RE_COND.search(rest or "")
242
- v = _RE_VERDICT.search(rest or "")
243
- r = _RE_REASON.search(rest or "")
244
- f = _RE_FG.search(rest or "")
245
- fg = f.group(1) if f else None
246
- try:
247
- fg = float(fg) if fg is not None else None
248
- except ValueError:
249
- fg = None
250
- reason = r.group(1).strip() if r else None
251
- return {"cond_hash": m.group(1) if m else None,
252
- "verdict": v.group(1) if v else None,
253
- "reason": reason or None,
254
- "fg_ratio": fg}
255
-
256
-
257
- # 生效条件:content 无正文行(空串、全为注释/空行、或无 ROOT_MARK 且不匹配 _RE_PART)返回 None;首行含 ROOT_MARK 返回 kind="root" 记录(n_parts 由 _RE_NPARTS 转 int、未命中为 None,cond_hash 取自 _fields);否则须匹配 _RE_PART,不匹配返回 None,匹配后 bbox 按逗号切分对非空项做 int(float(x))(ValueError 则 bbox=None)并返回 kind="part" 记录。
258
- def parse_node(content: str):
259
- """视觉节点正文 → 结构化记录;非视觉节点 → None。"""
260
- line = _find_body_line(content)
261
- if not line:
262
- return None
263
- if ROOT_MARK in line:
264
- d = _fields(line)
265
- n = _RE_NPARTS.search(line)
266
- return {"kind": "root", "type": None, "bbox": None,
267
- "n_parts": int(n.group(1)) if n else None,
268
- "cond_hash": d["cond_hash"], "verdict": None,
269
- "reason": None, "fg_ratio": None}
270
- m = _RE_PART.match(line)
271
- if not m:
272
- return None
273
- try:
274
- bbox = [int(float(x)) for x in m.group("bbox").split(",") if x.strip()]
275
- except ValueError:
276
- bbox = None
277
- d = _fields(m.group("rest"))
278
- d.update({"kind": "part", "type": m.group("type").strip(), "bbox": bbox})
279
- return d
280
-
281
-
282
- # ---- 节点集合 -------------------------------------------------------------
283
-
284
- # 生效条件:layer 透传给 cg._candidates,nid 取 e["id"] 或 path 去 .md 后须以 prefixes 中任一开头且 cg._read 返回非 None 的 fm 才被计数;加密内容记录 locked=True/parsed=None 且不触发 limit 检查,非加密内容 parse_node 后若 limit 非 None 且节点数已达 limit 即 break(因此最多多计该条)。
285
- def _vision_nodes(cg, layer=None, prefixes=VISION_PREFIXES, limit=None) -> list:
286
- """收集视觉节点(只读 index)。
287
-
288
- `layer=None` → 全层扫描(按前缀识别);不静默漏节点——G4 阶段被 fail-closed
289
- 跳过、仍留在原层的密文视觉节点也必须被计入 `skipped_locked` 而非被忽略。
290
- """
291
- nodes = []
292
- for e in cg._candidates(layer=layer) or []:
293
- nid = e.get("id") or os.path.basename(e.get("path") or "")[:-3]
294
- if not any(nid.startswith(p) for p in prefixes):
295
- continue
296
- fm, content = cg._read(e)
297
- if fm is None:
298
- continue
299
- if crypto.is_encrypted(content):
300
- nodes.append({"id": nid, "path": e["path"], "layer": e.get("layer"),
301
- "tags": list(fm.get("tags") or []), "fm": fm,
302
- "locked": True, "parsed": None})
303
- continue
304
- nodes.append({"id": nid, "path": e["path"], "layer": e.get("layer"),
305
- "tags": list(fm.get("tags") or []), "fm": fm,
306
- "locked": False, "parsed": parse_node(content)})
307
- if limit is not None and len(nodes) >= limit:
308
- break
309
- return nodes
310
-
311
-
312
- # 生效条件:nodes 中 parsed 为 dict、kind=="root" 且 cond_hash 为真值的节点,以其 tags[-1](无 tags 时为空串)为标签 setdefault 记录首个 cond_hash,返回标签→cond_hash 的 out。
313
- def _family_root_cond(nodes) -> dict:
314
- """家族标签(image_id) → 根节点 cond_hash。"""
315
- out = {}
316
- for n in nodes:
317
- p = n.get("parsed")
318
- if p and p.get("kind") == "root" and p.get("cond_hash"):
319
- label = n["tags"][-1] if n["tags"] else ""
320
- out.setdefault(label, p["cond_hash"])
321
- return out
322
-
323
-
324
- # 生效条件:n["id"] 以 "vpipe_" 开头时先以 roots.get(tags[-1] 或 "") 取 cond——cond 为假返回 (None,"no_family_root"),cond 为真则在 sources 中匹配 identity_cond_hash 成功返回 (s,"identity_cond_hash")、未命中再按该标签匹配 image_id 成功返回 (s,"image_id")、仍失败返回 (None,"no_source_archive");非 vpipe_ 时取 tags 中首个匹配 _RE_IMG_TAG 的 img<N>(未取到则不匹配)按 image_id 命中返回 (s,"image_tag"),否则返回 (None,"no_source_archive")。
325
- def _pick_source(n, sources, roots) -> tuple:
326
- """→ (source, joined_by);无法定位图集 → (None, 原因)。"""
327
- nid = n["id"]
328
- tags = n["tags"]
329
- if nid.startswith("vpipe_"):
330
- cond = roots.get(tags[-1] if tags else "")
331
- if cond:
332
- for s in sources:
333
- if s.get("identity_cond_hash") == cond:
334
- return s, "identity_cond_hash"
335
- else:
336
- return None, "no_family_root"
337
- label = tags[-1] if tags else ""
338
- for s in sources:
339
- if label and s.get("image_id") == label:
340
- return s, "image_id"
341
- return None, "no_source_archive"
342
- # imgpart:标签 `img<N>` ↔ 归档 image_id == N
343
- img = None
344
- for t in tags:
345
- m = _RE_IMG_TAG.match(str(t))
346
- if m:
347
- img = m.group(1)
348
- break
349
- if img is not None:
350
- for s in sources:
351
- if s.get("image_id") == img:
352
- return s, "image_tag"
353
- return None, "no_source_archive"
354
-
355
-
356
- # ---- 三档映射 -------------------------------------------------------------
357
-
358
- # 生效条件:parsed.cond_hash 为真值时按 str(cond_hash) 从 src["by_cond"] 取候选——其中 type 与 parsed["type"] 相同者直接返回 (r,"cond_hash"),否则候选仅 1 条返回 (cands[0],"cond_hash")、多于 1 条返回 (None,None);候选为空或 cond_hash 为假时若 parsed.type 为真则按 src["by_type"].get(type) 命中返回 (r,"type"),否则返回 (None,None)。
359
- def _match_record(src, parsed, joined_by):
360
- """在归档里定位对应部件记录(imgpart 优先 cond_hash 精确,vpipe 按 type)。"""
361
- if parsed.get("cond_hash"):
362
- cands = list(src["by_cond"].get(str(parsed["cond_hash"])) or [])
363
- if cands:
364
- for r in cands:
365
- if parsed.get("type") and str(r.get("type")) == parsed["type"]:
366
- return r, "cond_hash"
367
- return (cands[0], "cond_hash") if len(cands) == 1 else (None, None)
368
- if parsed.get("type"):
369
- r = src["by_type"].get(parsed["type"])
370
- if r:
371
- return r, "type"
372
- return None, None
373
-
374
-
375
- # 生效条件:n["locked"] 为真→BLINDSPOT(reason="locked");否则 parsed 缺失→"unparsed"、kind 非 "part"→"root_no_verdict";否则 _pick_source(n,sources,roots) 无源→以 why 为 reason;否则 _match_record 无记录→"no_matching_part";否则 parsed 与 rec 的 verdict 均为真且不等→"verdict_mismatch";否则 TIER2_FIELDS 中任一字段为 None→"missing_field:…";全部通过才返回 STATUS_WHITEBOX 与 ev(未用到的形参 aeis_root_used 不参与判定)。
376
- def build_evidence(n, sources, roots, aeis_root_used):
377
- """单节点 → (status, evidence, meta);严格三档,缺源即 BLINDSPOT。"""
378
- parsed = n.get("parsed")
379
- if n.get("locked"):
380
- return STATUS_BLINDSPOT, None, {"reason": "locked",
381
- "source": None, "joined_by": None}
382
- if not parsed or parsed.get("kind") != "part":
383
- return STATUS_BLINDSPOT, None, {"reason": "root_no_verdict"
384
- if parsed else "unparsed",
385
- "source": None, "joined_by": None}
386
- src, why = _pick_source(n, sources, roots)
387
- if src is None:
388
- return STATUS_BLINDSPOT, None, {"reason": why, "source": None,
389
- "joined_by": None}
390
- rec, joined_by = _match_record(src, parsed, why)
391
- if rec is None:
392
- return STATUS_BLINDSPOT, None, {"reason": "no_matching_part",
393
- "source": src, "joined_by": why}
394
- # 白箱优先、条件一致校验:verdict 不一致即不落半可信证据
395
- if parsed.get("verdict") and rec.get("verdict") \
396
- and parsed["verdict"] != rec["verdict"]:
397
- return STATUS_BLINDSPOT, None, {"reason": "verdict_mismatch",
398
- "source": src, "joined_by": joined_by}
399
- ev = {
400
- "algo": rec.get("algo") or src.get("algo"),
401
- "confidence": rec.get("confidence"),
402
- "cond_hash": parsed.get("cond_hash") or rec.get("cond_hash"),
403
- "fg_ratio": parsed.get("fg_ratio")
404
- if parsed.get("fg_ratio") is not None else rec.get("fg_ratio"),
405
- "occluded": rec.get("occluded"),
406
- "verdict_reason": parsed.get("reason") or rec.get("verdict_reason"),
407
- }
408
- gaps = [k for k in TIER2_FIELDS if ev.get(k) is None]
409
- if gaps:
410
- return STATUS_BLINDSPOT, None, {"reason": "missing_field:" + ",".join(gaps),
411
- "source": src, "joined_by": joined_by}
412
- return STATUS_WHITEBOX, ev, {"reason": None, "source": src,
413
- "joined_by": joined_by}
414
-
415
-
416
- # ---- 预演 / 执行 / 回滚 / 留痕 --------------------------------------------
417
-
418
- # 生效条件:x 经 _as_cg 转换;prefixes 为假值回落 VISION_PREFIXES、aeis_root_ 为假值回落 aeis_root();ids 为真值时才按 set(ids) 过滤 nodes;对 nodes 调 build_evidence,locked 节点只累加 skipped_locked,白箱项入 items、其余入 blindspot_items,全程不写盘并返回含 aeis_root/sources/nodes_scanned/targeted/blindspot/by_reason 的报表。
419
- def plan(x, layer=None, prefixes=None, limit=None,
420
- aeis_root_=None, ids=None) -> dict:
421
- """预演:产出证据回填清单,不写盘。"""
422
- cg = _as_cg(x)
423
- prefixes = tuple(prefixes) if prefixes else VISION_PREFIXES
424
- root_used = aeis_root_ or aeis_root()
425
- sources = load_sources(root_used)
426
- nodes = _vision_nodes(cg, layer=layer, prefixes=prefixes, limit=limit)
427
- if ids:
428
- want = set(ids)
429
- nodes = [n for n in nodes if n["id"] in want]
430
- roots = _family_root_cond(_vision_nodes(cg, layer=layer,
431
- prefixes=prefixes))
432
- items, blind, locked = [], [], 0
433
- for n in nodes:
434
- status, ev, meta = build_evidence(n, sources, roots, root_used)
435
- if n.get("locked"):
436
- locked += 1
437
- continue
438
- row = {"id": n["id"], "layer": n.get("layer"), "status": status,
439
- "type": (n.get("parsed") or {}).get("type"),
440
- "reason": meta.get("reason"),
441
- "joined_by": meta.get("joined_by"),
442
- "source": _gallery_ref(meta["source"]["gallery"])
443
- if meta.get("source") else None,
444
- "already": n["fm"].get("evidence_status")}
445
- if status == STATUS_WHITEBOX:
446
- row["evidence"] = ev
447
- items.append(row)
448
- else:
449
- blind.append(row)
450
- by_reason = {}
451
- for r in blind:
452
- k = r.get("reason") or "?"
453
- by_reason[k] = by_reason.get(k, 0) + 1
454
- return {
455
- "root": cg.root, "dry_run": True, "action": "vision_evidence",
456
- "aeis_root": root_used, "authority_doc": AUTHORITY_DOC,
457
- "sources": [{"gallery": _gallery_ref(s["gallery"]),
458
- "image_id": s["image_id"],
459
- "parts": len(s["by_type"])} for s in sources],
460
- "nodes_scanned": len(nodes), "skipped_locked": locked,
461
- "targeted": len(items), "blindspot": len(blind),
462
- "blindspot_by_reason": by_reason,
463
- "items": items, "blindspot_items": blind,
464
- }
465
-
466
-
467
- # 生效条件:x 经 _as_cg,batch 为假值回落 BATCH_DEFAULT 并交给 _unique_batch(cg, …) 去重;ids 为真值才按 id 过滤、entry_ids 为真值才按 _entry_id(batch,id) 过滤;循环中节点不在 cg.index["nodes"] 记 skipped_drift,fm 为 None 或内容加密记 skipped_locked,fm 已有同 status(白箱还要求 evidence 相同)记 skipped_already,否则改写 fm 并落盘、追加 jsonl、收集 entry_id;written 非 0 时 cg.rebuild_index() 并尝试 evolution.record(异常被吞)后返回 rep。
468
- def apply(x, ids=None, entry_ids=None, layer=None, prefixes=None,
469
- limit=None, batch=None, aeis_root_=None, actor=None) -> dict:
470
- """执行回填:逐节点改写 frontmatter 证据面,写 `_vision_evidence.jsonl`。"""
471
- cg = _as_cg(x)
472
- p = plan(cg, layer=layer, prefixes=prefixes, limit=limit,
473
- aeis_root_=aeis_root_)
474
- batch = _unique_batch(cg, batch or BATCH_DEFAULT)
475
- want_ids = set(ids) if ids else None
476
- want_eids = set(entry_ids) if entry_ids else None
477
- rows = list(p["items"]) + list(p["blindspot_items"])
478
- if want_ids is not None:
479
- rows = [r for r in rows if r["id"] in want_ids]
480
- if want_eids is not None:
481
- rows = [r for r in rows
482
- if _entry_id(batch, r["id"]) in want_eids]
483
- rep = {"root": cg.root, "dry_run": False, "action": "vision_evidence",
484
- "batch": batch, "actor": actor, "aeis_root": p["aeis_root"],
485
- "authority_doc": AUTHORITY_DOC,
486
- "planned": len(rows), "written": 0, "blindspot_written": 0,
487
- "skipped_locked": p["skipped_locked"],
488
- "skipped_already": 0, "skipped_drift": 0, "entry_ids": []}
489
- for r in rows:
490
- nid = r["id"]
491
- e = cg.index["nodes"].get(nid)
492
- if not e:
493
- rep["skipped_drift"] += 1
494
- continue
495
- fm, content = cg._read(e)
496
- if fm is None or crypto.is_encrypted(content):
497
- rep["skipped_locked"] += 1
498
- continue
499
- status = r["status"]
500
- ev = r.get("evidence")
501
- if fm.get("evidence_status") == status and \
502
- (status == STATUS_BLINDSPOT or fm.get("evidence") == ev):
503
- rep["skipped_already"] += 1
504
- continue
505
- fm.pop("evidence", None)
506
- if status == STATUS_WHITEBOX:
507
- fm["evidence"] = ev
508
- fm["evidence_status"] = STATUS_WHITEBOX
509
- fm["evidence_tier"] = 2
510
- fm["evidence_joined_by"] = r.get("joined_by")
511
- fm["evidence_source"] = ("aeis:vision:%s" % r["source"]
512
- if r.get("source") else None)
513
- rep["written"] += 1
514
- else:
515
- fm["evidence_status"] = STATUS_BLINDSPOT
516
- fm["evidence_blindspot_reason"] = r.get("reason")
517
- rep["blindspot_written"] += 1
518
- rep["written"] += 1
519
- fm["evidence_doc"] = AUTHORITY_DOC
520
- fm["evidence_batch"] = batch
521
- fm["evidence_at"] = time.time()
522
- cg._write_node(nid, os.path.join(cg.root, e["path"]), fm, content,
523
- durable=True)
524
- append_jsonl(_log_path(cg), {
525
- "action": "vision_evidence", "ts": time.time(), "batch": batch,
526
- "actor": actor, "entry_id": _entry_id(batch, nid), "node": nid,
527
- "layer": e.get("layer"), "status": status, "tier": 2
528
- if status == STATUS_WHITEBOX else 3,
529
- "evidence": ev, "reason": r.get("reason"),
530
- "source": r.get("source"), "joined_by": r.get("joined_by")})
531
- rep["entry_ids"].append(_entry_id(batch, nid))
532
- if rep["written"]:
533
- cg.rebuild_index()
534
- try:
535
- evolution.record(
536
- cg, kind=evolution.KIND_GENERAL,
537
- pattern=("视觉证据面缺口的闭合方式:以只读归档逐部件结构化结果"
538
- "(cond_hash 连接)反填节点证据,脱敏为图集编号引用"),
539
- action="vision_evidence",
540
- evidence=("batch=%s written=%d whitebox=%d blindspot=%d"
541
- % (batch, rep["written"], len(p["items"]),
542
- rep["blindspot_written"])),
543
- source="data/vision(只读,本仓)",
544
- extra={"batch": batch, "authority_doc": AUTHORITY_DOC})
545
- except Exception: # noqa: BLE001
546
- pass # 留痕失败不拖垮批次
547
- rep["blindspot"] = len(p["blindspot_items"])
548
- return rep
549
-
550
-
551
- # 生效条件:x 经 _as_cg 后读 _log_path(cg) 日志,只处理 action=="vision_evidence" 记录;batch 为真值时仅取 batch 相同记录、entry_ids 为真值时仅取 entry_id 在集合内记录、该 entry_id 已出现在 rollback 日志则记 skipped_done;节点缺失或 _read 返回 fm 为 None 记 missing,EVIDENCE_KEYS 一个都不在 fm 中记 skipped_done,否则删除命中键、写盘并追加 rollback 留痕,reverted 非 0 时重建索引后返回 rep。
552
- def rollback(x, batch=None, entry_ids=None, actor=None) -> dict:
553
- """按留痕反向应用:删除本批次写入的证据键(幂等,防覆盖)。"""
554
- cg = _as_cg(x)
555
- want = set(entry_ids) if entry_ids else None
556
- rep = {"root": cg.root, "action": "vision_evidence_rollback",
557
- "actor": actor, "batch": batch, "reverted": 0, "missing": 0,
558
- "skipped_done": 0, "cleared_keys": 0}
559
- log = list(read_jsonl(_log_path(cg)) or [])
560
- done = {r.get("entry_id") for r in log
561
- if r.get("action") == "vision_evidence_rollback"
562
- and r.get("entry_id")}
563
- for rec in log:
564
- if rec.get("action") != "vision_evidence":
565
- continue
566
- if batch and rec.get("batch") != batch:
567
- continue
568
- eid = rec.get("entry_id")
569
- if want is not None and eid not in want:
570
- continue
571
- if eid in done:
572
- rep["skipped_done"] += 1
573
- continue
574
- nid = rec.get("node")
575
- e = cg.index["nodes"].get(nid)
576
- if not e:
577
- rep["missing"] += 1
578
- continue
579
- fm, content = cg._read(e)
580
- if fm is None:
581
- rep["missing"] += 1
582
- continue
583
- cleared = 0
584
- for k in EVIDENCE_KEYS:
585
- if k in fm:
586
- fm.pop(k, None)
587
- cleared += 1
588
- if not cleared:
589
- rep["skipped_done"] += 1
590
- continue
591
- cg._write_node(nid, os.path.join(cg.root, e["path"]), fm, content,
592
- durable=True)
593
- append_jsonl(_log_path(cg), {
594
- "action": "vision_evidence_rollback", "ts": time.time(),
595
- "actor": actor, "batch": rec.get("batch"), "entry_id": eid,
596
- "node": nid, "cleared_keys": cleared})
597
- rep["reverted"] += 1
598
- rep["cleared_keys"] += cleared
599
- if rep["reverted"]:
600
- cg.rebuild_index()
601
- return rep
602
-
603
-
604
- # 生效条件:x 经 _as_cg 后逐条读 _log_path(cg),batch 为真值时才按 rec.get("batch")==batch 过滤;limit 非 None 且 >=0 时执行 recs = recs[-limit:](limit=0 因 -0 切片退化为全量),limit 为 None 或负数时不截断,返回 {root,total,returned,records}。
605
- def history(x, limit=100, batch=None) -> dict:
606
- cg = _as_cg(x)
607
- recs = []
608
- for rec in read_jsonl(_log_path(cg)) or []:
609
- if batch and rec.get("batch") != batch:
610
- continue
611
- recs.append(rec)
612
- total = len(recs)
613
- if limit is not None and limit >= 0:
614
- recs = recs[-limit:]
615
- return {"root": cg.root, "total": total, "returned": len(recs),
616
- "records": recs}
617
-
618
-
619
- # ---- CLI(真实库预演/执行用;MCP 侧走 maintain action) -------------------
620
-
621
- # 生效条件:argv 为 None 时 argparse 取 sys.argv;--prefixes 默认由 ",".join(VISION_PREFIXES) 提供并切出非空前缀;a.rollback 为真调 rollback(entry_ids 切分后为空则传 None)、否则 a.apply 为真调 apply、否则调 plan;--json 为真打印整份 JSON,否则按固定关键字打印并恒返回 0。
622
- def _main(argv=None) -> int:
623
- import argparse
624
- ap = argparse.ArgumentParser(description="G5 视觉证据回填(默认只预演)")
625
- ap.add_argument("root", help="认知图库根")
626
- ap.add_argument("--aeis-root", default=None, help="AEIS 仓库根(只读证据源)")
627
- ap.add_argument("--layer", default=None, help="限定层;缺省全层(按前缀)")
628
- ap.add_argument("--prefixes", default=",".join(VISION_PREFIXES))
629
- ap.add_argument("--limit", type=int, default=None)
630
- ap.add_argument("--batch", default=None)
631
- ap.add_argument("--actor", default="maintain")
632
- ap.add_argument("--apply", action="store_true", help="真正写盘(默认预演)")
633
- ap.add_argument("--rollback", action="store_true", help="按批次/定向回滚")
634
- ap.add_argument("--entry-ids", default=None)
635
- ap.add_argument("--json", action="store_true", help="输出完整 JSON 报表")
636
- a = ap.parse_args(argv)
637
- prefixes = [p for p in (a.prefixes or "").split(",") if p]
638
- if a.rollback:
639
- out = rollback(a.root, batch=a.batch,
640
- entry_ids=[x for x in (a.entry_ids or "").split(",") if x]
641
- or None, actor=a.actor)
642
- elif a.apply:
643
- out = apply(a.root, layer=a.layer, prefixes=prefixes, limit=a.limit,
644
- batch=a.batch, aeis_root_=a.aeis_root, actor=a.actor)
645
- else:
646
- out = plan(a.root, layer=a.layer, prefixes=prefixes, limit=a.limit,
647
- aeis_root_=a.aeis_root)
648
- if a.json:
649
- print(json.dumps(out, ensure_ascii=False, indent=2))
650
- else:
651
- for k in ("root", "aeis_root", "nodes_scanned", "targeted", "blindspot",
652
- "blindspot_by_reason", "written", "blindspot_written",
653
- "skipped_locked", "skipped_already", "batch", "reverted"):
654
- if k in out:
655
- print("%-22s %s" % (k, out[k]))
656
- for s in out.get("sources") or []:
657
- print(" source %s image_id=%s parts=%s"
658
- % (s["gallery"], s["image_id"], s["parts"]))
659
- if out.get("items"):
660
- print("targeted sample:",
661
- [(i["id"], i["joined_by"], (i["evidence"] or {}).get("algo"))
662
- for i in out["items"][:3]])
663
- return 0
664
-
665
-
666
- if __name__ == "__main__": # pragma: no cover
1
+ # -*- coding: utf-8 -*-
2
+ """G5 · 视觉证据回填(守卫式 / 零 LLM / 不读图像 / 不重跑视觉)。
3
+
4
+ 裁定依据
5
+ --------
6
+ `docs/mdcg/认知图_G4-G8缺口裁定单_v0.1.md` §三(四态 = ACCEPT,条件 = 脱敏 + 只读 AEIS)。
7
+ 缺口根因(库外只读观测):产出侧 `vision_pipeline.cg_ingest` 只取
8
+ `p.get("model_evidence")`,白箱 `geometry_parts` 部件不带该键 → 部件节点证据面
9
+ 恒为 `{}`。即「证据口径未定义」,不是「没有证据」。
10
+
11
+ 证据源(**主证据源**,只读、不落图、不落敏感语义文本)
12
+ ------------------------------------------------------
13
+ · `AEIS/data/vision/<图集>/parts_*.json`(如 `parts_0.json`)
14
+ · `AEIS/data/vision/<图集>/vision_*.json`(同 schema:parts 带全部判定字段)
15
+ · 权威口径文档:`AEIS/data/vision/VISION_PIPELINE_已验证_v1.md`
16
+ 关键:**库内节点正文本身**也是同一白箱管线的持久化产物(部件行含
17
+ `bbox / cond_hash / verdict / reason / fg`),可与归档逐部件结构化结果互证。
18
+
19
+ 三档映射(裁定单 §三,逐字段必得有源,缺一不落)
20
+ ------------------------------------------------
21
+ · tier 1 模型证据:源数据含 `model_evidence` → `{model, kpts_used, min_conf}`。
22
+ 本库归档与节点均无该键 → 本轮恒不适用。
23
+ · tier 2 白箱证据:无模型键但判定要素齐 → 落
24
+ `{algo, confidence, cond_hash, fg_ratio, occluded, verdict_reason}`。
25
+ · tier 3 盲区:两者皆无 → `evidence` 保持为空,标 `evidence_status=BLINDSPOT`
26
+ (附 `evidence_blindspot_reason`),**不编造**。
27
+
28
+ 归因纪律(白箱优先、模型次之、缺失不编造)
29
+ ------------------------------------------
30
+ · 节点正文已记录者优先取节点实测(`cond_hash / fg_ratio / verdict_reason`);
31
+ 节点未记录者(`algo / confidence / occluded`)由归档补全。
32
+ · 连接键可验证:imgpart 家族用 `cond_hash` 精确连接;vpipe 家族用
33
+ (家族根 `cond_hash` → 归档 `identity.cond_hash`)+ `type` 连接。
34
+ · 连接后必须校验 `verdict` 一致;不一致 → 判 BLINDSPOT(`verdict_mismatch`),
35
+ 不落半可信证据。
36
+ · 6 字段任一取不到源 → BLINDSPOT(`missing_field:<name>`)。
37
+ · 根节点(`部件树根`)不承载四态裁定 → 恒 BLINDSPOT(`root_no_verdict`)。
38
+
39
+ 脱敏
40
+ ----
41
+ · 不读取任何图像文件;不重跑视觉;证据内只落结构化字段。
42
+ · 图集目录名不进库:一律以编号引用(`图集_0`…`图集_9`,取自目录尾部 `_<N>`)。
43
+ · `verdict_reason` 为算法产出的结构化判定理由(非敏感语义文本),且节点正文
44
+ 原本已含该字段,故不构成新增泄露面。
45
+
46
+ 纪律(对齐 backfill / consolidate)
47
+ -----------------------------------
48
+ · 不猜测:字段只在有源时写,来源写进 `evidence_source` / `evidence_joined_by`。
49
+ · 可预演:`plan()` 只出报表;`apply()` 才写。
50
+ · 可留痕:每次写入记一条 `_vision_evidence.jsonl`(批次 / 节点 / 档位 / 来源 /
51
+ 连接键 / 写入字段)。
52
+ · 可回滚:`rollback()` 按留痕反向删除本批次写入的证据键(幂等,防覆盖)。
53
+ · fail-closed:密文节点一律跳过,绝不解密回写。
54
+ · 只落 frontmatter 证据面:不动正文(含正文内联 `evidence={}` 槽),
55
+ 避免改 `content_hash` 破坏上游去重——列为未闭合项。
56
+ """
57
+ from __future__ import annotations
58
+
59
+ import glob
60
+ import json
61
+ import os
62
+ import re
63
+ import time
64
+
65
+ from . import crypto, evolution
66
+ from .fsutil import append_jsonl, read_jsonl
67
+ from .mdcos import MdCGOS
68
+ from .readcache import direct_read
69
+
70
+ # ---- 常量 -----------------------------------------------------------------
71
+
72
+ EVIDENCE_LOG = "_vision_evidence.jsonl"
73
+
74
+ #: 视觉节点 id 前缀(G4 归位后位于 contextual 层)
75
+ VISION_PREFIXES = ("imgpart_", "vpipe_")
76
+
77
+ #: 视觉证据归档根(**本仓** data/vision,随大脑自带;只读)。
78
+ #: 可用环境变量覆盖;`MDCG_AEIS_ROOT` 为三层拆分前的遗留名,仍兼容。
79
+ VISION_ROOT_ENV = "MDCG_VISION_ROOT"
80
+ LEGACY_VISION_ROOT_ENV = "MDCG_AEIS_ROOT"
81
+ _HERE = os.path.dirname(os.path.abspath(__file__))
82
+ #: 默认 = 本仓根 → 证据落在 <repo>/data/vision(拆分子项 B7:不再指向外部 AEIS 仓)
83
+ DEFAULT_VISION_ROOT = os.path.dirname(_HERE)
84
+
85
+ #: 权威口径文档(只读引用,写进报表供核对;相对证据归档根)
86
+ AUTHORITY_DOC = "data/vision/VISION_PIPELINE_已验证_v1.md"
87
+
88
+ #: 裁定单 §三 tier-2 六字段:逐字段必得有源,缺一即不落(→ BLINDSPOT)
89
+ TIER2_FIELDS = ("algo", "confidence", "cond_hash", "fg_ratio", "occluded",
90
+ "verdict_reason")
91
+
92
+ STATUS_MODEL = "MODEL"
93
+ STATUS_WHITEBOX = "WHITEBOX"
94
+ STATUS_BLINDSPOT = "BLINDSPOT"
95
+
96
+ #: 本轮写入的全部 frontmatter 证据键(回滚据此删除)
97
+ EVIDENCE_KEYS = ("evidence", "evidence_status", "evidence_tier",
98
+ "evidence_source", "evidence_joined_by", "evidence_doc",
99
+ "evidence_blindspot_reason", "evidence_batch", "evidence_at")
100
+
101
+ BATCH_DEFAULT = "visevid"
102
+
103
+ ROOT_MARK = "部件树根"
104
+
105
+ # 部件行:`<head> 部件 <type>: bbox=[..] <rest>`
106
+ _RE_PART = re.compile(
107
+ r"^(?P<head>.+?)\s+部件\s+(?P<type>[^::]+)\s*[::]\s*"
108
+ r"bbox=\[(?P<bbox>[^\]]*)\]\s*(?P<rest>.*)$")
109
+ _RE_COND = re.compile(r"cond_hash=([0-9a-fA-F]{6,})")
110
+ _RE_VERDICT = re.compile(r"(?:^|\s)verdict=([A-Za-z]+)")
111
+ _RE_REASON = re.compile(r"(?:^|\s)reason=(.*?)(?:\s+fg=|\s+evidence=|$)")
112
+ _RE_FG = re.compile(r"(?:^|\s)fg=([0-9]*\.?[0-9]+)")
113
+ _RE_NPARTS = re.compile(r"(\d+)\s*部件")
114
+ _RE_IMG_TAG = re.compile(r"^img(\d+)$")
115
+ _RE_DIR_N = re.compile(r"_(\d+)$")
116
+
117
+
118
+ # ---- 通用工具 -------------------------------------------------------------
119
+
120
+ # 生效条件:x 为 str 时返回 MdCGOS(x) 新实例,否则原样返回 x。
121
+ def _as_cg(x):
122
+ """接受 root 路径或已构造 cg 实例——保持密级隔离与密钥上下文。"""
123
+ return MdCGOS(x) if isinstance(x, str) else x
124
+
125
+
126
+ # 生效条件:无必需形参,调用即返回 time.strftime("%Y%m%d-%H%M%S") 的当前批次串。
127
+ def _now_batch() -> str:
128
+ return time.strftime("%Y%m%d-%H%M%S")
129
+
130
+
131
+ # 生效条件:base 为字符串批号,先读 _log_path(cg) 的 jsonl 收集 batch 字段中以 base 开头的已有值,base 未被占用则原样返回 base,已占用则返回首个未占用的 f"{base}.{i}"(i 从 2 递增)。
132
+ def _unique_batch(cg, base: str) -> str:
133
+ """同秒重复调用时批号去重(后缀 .2/.3…),保证按批次回滚不打偏。"""
134
+ seen = set()
135
+ for rec in read_jsonl(_log_path(cg)) or []:
136
+ b = rec.get("batch")
137
+ if isinstance(b, str) and b.startswith(base):
138
+ seen.add(b)
139
+ if base not in seen:
140
+ return base
141
+ i = 2
142
+ while f"{base}.{i}" in seen:
143
+ i += 1
144
+ return f"{base}.{i}"
145
+
146
+
147
+ # 生效条件:cg 具 root 属性时取 cg.root、否则取 str(cg) 作为 root,返回 os.path.join(root, EVIDENCE_LOG)。
148
+ def _log_path(cg) -> str:
149
+ root = cg.root if hasattr(cg, "root") else str(cg)
150
+ return os.path.join(root, EVIDENCE_LOG)
151
+
152
+
153
+ # 生效条件:batch 与 nid 恒以 "%s|%s" 拼接成条目号,不做空值或类型校验。
154
+ def _entry_id(batch: str, nid: str) -> str:
155
+ return "%s|%s" % (batch, nid)
156
+
157
+
158
+ # 生效条件:v 为 list/tuple 时返回各元素 str(x).strip() 后非空项以「;」连接;否则 v 为 None 返回空串,其余值返回 str(v).strip()。
159
+ def _as_text(v) -> str:
160
+ if isinstance(v, (list, tuple)):
161
+ return ";".join(str(x).strip() for x in v if str(x).strip())
162
+ return "" if v is None else str(v).strip()
163
+
164
+
165
+ # 生效条件:path 经 abspath→dirname→basename 取名后匹配 _RE_DIR_N,命中则返回 int(m.group(1)),未命中返回 None。
166
+ def _gallery_no(path: str):
167
+ """图集编号:目录名尾部 `_<N>`;缺省 None(脱敏引用用)。"""
168
+ name = os.path.basename(os.path.dirname(os.path.abspath(path)))
169
+ m = _RE_DIR_N.search(name)
170
+ return int(m.group(1)) if m else None
171
+
172
+
173
+ # 生效条件:gal 非 None 时返回 "图集_%s" % gal,gal 为 None 时返回 "图集_?"。
174
+ def _gallery_ref(gal) -> str:
175
+ return "图集_%s" % (gal if gal is not None else "?")
176
+
177
+
178
+ # 生效条件:无必需形参,按 os.environ.get(VISION_ROOT_ENV) or os.environ.get(LEGACY_VISION_ROOT_ENV) or DEFAULT_VISION_ROOT 取值——某环境变量为空串时视为假值继续回落下一项。
179
+ def vision_root() -> str:
180
+ """视觉证据归档根:env 覆盖 > 遗留 env > 本仓 data/vision 的父目录。"""
181
+ return (os.environ.get(VISION_ROOT_ENV)
182
+ or os.environ.get(LEGACY_VISION_ROOT_ENV)
183
+ or DEFAULT_VISION_ROOT)
184
+
185
+
186
+ #: 遗留别名(拆分前命名);新代码请用 vision_root()。
187
+ aeis_root = vision_root
188
+
189
+
190
+ # ---- 证据源(只读归档) ---------------------------------------------------
191
+
192
+ # 生效条件:root 下 data/vision/*/*.json 逐文件读;文件 OSError/ValueError、JSON 顶层非 dict、parts 非非空 list、或过滤后(type 与 cond_hash 皆真值的 dict)无记录时跳过该文件,否则收入含 path/gallery/image_id/algo/identity_cond_hash/by_type/by_cond 的 src 并最终返回 out 列表。
193
+ def load_sources(root: str) -> list:
194
+ """扫描 `AEIS/data/vision/*/*.json`,取逐部件结构化结果(主证据源)。"""
195
+ base = os.path.join(root, "data", "vision")
196
+ out = []
197
+ for p in sorted(glob.glob(os.path.join(base, "*", "*.json"))):
198
+ try:
199
+ with open(p, encoding="utf-8") as f:
200
+ d = json.load(f)
201
+ except (OSError, ValueError):
202
+ continue
203
+ if not isinstance(d, dict):
204
+ continue
205
+ parts = d.get("parts")
206
+ if not isinstance(parts, list) or not parts:
207
+ continue
208
+ recs = [x for x in parts
209
+ if isinstance(x, dict) and x.get("type") and x.get("cond_hash")]
210
+ if not recs:
211
+ continue
212
+ ident = d.get("identity") if isinstance(d.get("identity"), dict) else {}
213
+ src = {
214
+ "path": p, "gallery": _gallery_no(p),
215
+ "image_id": "" if d.get("image_id") is None else str(d.get("image_id")),
216
+ "algo": d.get("algo"),
217
+ "identity_cond_hash": ident.get("cond_hash"),
218
+ "by_type": {}, "by_cond": {},
219
+ }
220
+ for r in recs:
221
+ src["by_type"].setdefault(str(r["type"]), r)
222
+ src["by_cond"].setdefault(str(r["cond_hash"]), []).append(r)
223
+ out.append(src)
224
+ return out
225
+
226
+
227
+ # ---- 节点正文解析 ---------------------------------------------------------
228
+
229
+ # 生效条件:content 为 None 或空串时按 "" 处理;逐行 strip 后跳过空行与以 # 开头的行,返回首个含 ROOT_MARK 或匹配 _RE_PART 的行,全部无命中返回 ""。
230
+ def _find_body_line(content: str) -> str:
231
+ for ln in (content or "").split("\n"):
232
+ s = ln.strip()
233
+ if not s or s.startswith("#"):
234
+ continue
235
+ if ROOT_MARK in s or _RE_PART.match(s):
236
+ return s
237
+ return ""
238
+
239
+
240
+ # 生效条件:rest 为 None/假值时按 "" 处理,分别用 _RE_COND/_RE_VERDICT/_RE_REASON/_RE_FG 捕获;fg 命中则转 float、ValueError 时置 None;reason 命中并 strip 后为空则置 None;返回含 cond_hash/verdict/reason/fg_ratio 四键的 dict(未命中键值为 None)。
241
+ def _fields(rest: str) -> dict:
242
+ m = _RE_COND.search(rest or "")
243
+ v = _RE_VERDICT.search(rest or "")
244
+ r = _RE_REASON.search(rest or "")
245
+ f = _RE_FG.search(rest or "")
246
+ fg = f.group(1) if f else None
247
+ try:
248
+ fg = float(fg) if fg is not None else None
249
+ except ValueError:
250
+ fg = None
251
+ reason = r.group(1).strip() if r else None
252
+ return {"cond_hash": m.group(1) if m else None,
253
+ "verdict": v.group(1) if v else None,
254
+ "reason": reason or None,
255
+ "fg_ratio": fg}
256
+
257
+
258
+ # 生效条件:content 无正文行(空串、全为注释/空行、或无 ROOT_MARK 且不匹配 _RE_PART)返回 None;首行含 ROOT_MARK 返回 kind="root" 记录(n_parts 由 _RE_NPARTS 转 int、未命中为 None,cond_hash 取自 _fields);否则须匹配 _RE_PART,不匹配返回 None,匹配后 bbox 按逗号切分对非空项做 int(float(x))(ValueError 则 bbox=None)并返回 kind="part" 记录。
259
+ def parse_node(content: str):
260
+ """视觉节点正文 → 结构化记录;非视觉节点 → None。"""
261
+ line = _find_body_line(content)
262
+ if not line:
263
+ return None
264
+ if ROOT_MARK in line:
265
+ d = _fields(line)
266
+ n = _RE_NPARTS.search(line)
267
+ return {"kind": "root", "type": None, "bbox": None,
268
+ "n_parts": int(n.group(1)) if n else None,
269
+ "cond_hash": d["cond_hash"], "verdict": None,
270
+ "reason": None, "fg_ratio": None}
271
+ m = _RE_PART.match(line)
272
+ if not m:
273
+ return None
274
+ try:
275
+ bbox = [int(float(x)) for x in m.group("bbox").split(",") if x.strip()]
276
+ except ValueError:
277
+ bbox = None
278
+ d = _fields(m.group("rest"))
279
+ d.update({"kind": "part", "type": m.group("type").strip(), "bbox": bbox})
280
+ return d
281
+
282
+
283
+ # ---- 节点集合 -------------------------------------------------------------
284
+
285
+ # 生效条件:layer 透传给 cg._candidates,nid 取 e["id"] 或 path 去 .md 后须以 prefixes 中任一开头且 cg._read 返回非 None 的 fm 才被计数;加密内容记录 locked=True/parsed=None 且不触发 limit 检查,非加密内容 parse_node 后若 limit 非 None 且节点数已达 limit 即 break(因此最多多计该条)。
286
+ def _vision_nodes(cg, layer=None, prefixes=VISION_PREFIXES, limit=None) -> list:
287
+ """收集视觉节点(只读 index)。
288
+
289
+ `layer=None` → 全层扫描(按前缀识别);不静默漏节点——G4 阶段被 fail-closed
290
+ 跳过、仍留在原层的密文视觉节点也必须被计入 `skipped_locked` 而非被忽略。
291
+ """
292
+ nodes = []
293
+ for e in cg._candidates(layer=layer) or []:
294
+ nid = e.get("id") or os.path.basename(e.get("path") or "")[:-3]
295
+ if not any(nid.startswith(p) for p in prefixes):
296
+ continue
297
+ fm, content = direct_read(cg, e) # 收集读喂写面(plan→apply):走盘上真值
298
+ if fm is None:
299
+ continue
300
+ if crypto.is_encrypted(content):
301
+ nodes.append({"id": nid, "path": e["path"], "layer": e.get("layer"),
302
+ "tags": list(fm.get("tags") or []), "fm": fm,
303
+ "locked": True, "parsed": None})
304
+ continue
305
+ nodes.append({"id": nid, "path": e["path"], "layer": e.get("layer"),
306
+ "tags": list(fm.get("tags") or []), "fm": fm,
307
+ "locked": False, "parsed": parse_node(content)})
308
+ if limit is not None and len(nodes) >= limit:
309
+ break
310
+ return nodes
311
+
312
+
313
+ # 生效条件:nodes 中 parsed 为 dict、kind=="root" 且 cond_hash 为真值的节点,以其 tags[-1](无 tags 时为空串)为标签 setdefault 记录首个 cond_hash,返回标签→cond_hash 的 out。
314
+ def _family_root_cond(nodes) -> dict:
315
+ """家族标签(image_id) → 根节点 cond_hash。"""
316
+ out = {}
317
+ for n in nodes:
318
+ p = n.get("parsed")
319
+ if p and p.get("kind") == "root" and p.get("cond_hash"):
320
+ label = n["tags"][-1] if n["tags"] else ""
321
+ out.setdefault(label, p["cond_hash"])
322
+ return out
323
+
324
+
325
+ # 生效条件:n["id"] 以 "vpipe_" 开头时先以 roots.get(tags[-1] 或 "") 取 cond——cond 为假返回 (None,"no_family_root"),cond 为真则在 sources 中匹配 identity_cond_hash 成功返回 (s,"identity_cond_hash")、未命中再按该标签匹配 image_id 成功返回 (s,"image_id")、仍失败返回 (None,"no_source_archive");非 vpipe_ 时取 tags 中首个匹配 _RE_IMG_TAG 的 img<N>(未取到则不匹配)按 image_id 命中返回 (s,"image_tag"),否则返回 (None,"no_source_archive")。
326
+ def _pick_source(n, sources, roots) -> tuple:
327
+ """→ (source, joined_by);无法定位图集 → (None, 原因)。"""
328
+ nid = n["id"]
329
+ tags = n["tags"]
330
+ if nid.startswith("vpipe_"):
331
+ cond = roots.get(tags[-1] if tags else "")
332
+ if cond:
333
+ for s in sources:
334
+ if s.get("identity_cond_hash") == cond:
335
+ return s, "identity_cond_hash"
336
+ else:
337
+ return None, "no_family_root"
338
+ label = tags[-1] if tags else ""
339
+ for s in sources:
340
+ if label and s.get("image_id") == label:
341
+ return s, "image_id"
342
+ return None, "no_source_archive"
343
+ # imgpart:标签 `img<N>` ↔ 归档 image_id == N
344
+ img = None
345
+ for t in tags:
346
+ m = _RE_IMG_TAG.match(str(t))
347
+ if m:
348
+ img = m.group(1)
349
+ break
350
+ if img is not None:
351
+ for s in sources:
352
+ if s.get("image_id") == img:
353
+ return s, "image_tag"
354
+ return None, "no_source_archive"
355
+
356
+
357
+ # ---- 三档映射 -------------------------------------------------------------
358
+
359
+ # 生效条件:parsed.cond_hash 为真值时按 str(cond_hash) 从 src["by_cond"] 取候选——其中 type 与 parsed["type"] 相同者直接返回 (r,"cond_hash"),否则候选仅 1 条返回 (cands[0],"cond_hash")、多于 1 条返回 (None,None);候选为空或 cond_hash 为假时若 parsed.type 为真则按 src["by_type"].get(type) 命中返回 (r,"type"),否则返回 (None,None)。
360
+ def _match_record(src, parsed, joined_by):
361
+ """在归档里定位对应部件记录(imgpart 优先 cond_hash 精确,vpipe 按 type)。"""
362
+ if parsed.get("cond_hash"):
363
+ cands = list(src["by_cond"].get(str(parsed["cond_hash"])) or [])
364
+ if cands:
365
+ for r in cands:
366
+ if parsed.get("type") and str(r.get("type")) == parsed["type"]:
367
+ return r, "cond_hash"
368
+ return (cands[0], "cond_hash") if len(cands) == 1 else (None, None)
369
+ if parsed.get("type"):
370
+ r = src["by_type"].get(parsed["type"])
371
+ if r:
372
+ return r, "type"
373
+ return None, None
374
+
375
+
376
+ # 生效条件:n["locked"] 为真→BLINDSPOT(reason="locked");否则 parsed 缺失→"unparsed"、kind 非 "part"→"root_no_verdict";否则 _pick_source(n,sources,roots) 无源→以 why 为 reason;否则 _match_record 无记录→"no_matching_part";否则 parsed 与 rec 的 verdict 均为真且不等→"verdict_mismatch";否则 TIER2_FIELDS 中任一字段为 None→"missing_field:…";全部通过才返回 STATUS_WHITEBOX 与 ev(未用到的形参 aeis_root_used 不参与判定)。
377
+ def build_evidence(n, sources, roots, aeis_root_used):
378
+ """单节点 → (status, evidence, meta);严格三档,缺源即 BLINDSPOT。"""
379
+ parsed = n.get("parsed")
380
+ if n.get("locked"):
381
+ return STATUS_BLINDSPOT, None, {"reason": "locked",
382
+ "source": None, "joined_by": None}
383
+ if not parsed or parsed.get("kind") != "part":
384
+ return STATUS_BLINDSPOT, None, {"reason": "root_no_verdict"
385
+ if parsed else "unparsed",
386
+ "source": None, "joined_by": None}
387
+ src, why = _pick_source(n, sources, roots)
388
+ if src is None:
389
+ return STATUS_BLINDSPOT, None, {"reason": why, "source": None,
390
+ "joined_by": None}
391
+ rec, joined_by = _match_record(src, parsed, why)
392
+ if rec is None:
393
+ return STATUS_BLINDSPOT, None, {"reason": "no_matching_part",
394
+ "source": src, "joined_by": why}
395
+ # 白箱优先、条件一致校验:verdict 不一致即不落半可信证据
396
+ if parsed.get("verdict") and rec.get("verdict") \
397
+ and parsed["verdict"] != rec["verdict"]:
398
+ return STATUS_BLINDSPOT, None, {"reason": "verdict_mismatch",
399
+ "source": src, "joined_by": joined_by}
400
+ ev = {
401
+ "algo": rec.get("algo") or src.get("algo"),
402
+ "confidence": rec.get("confidence"),
403
+ "cond_hash": parsed.get("cond_hash") or rec.get("cond_hash"),
404
+ "fg_ratio": parsed.get("fg_ratio")
405
+ if parsed.get("fg_ratio") is not None else rec.get("fg_ratio"),
406
+ "occluded": rec.get("occluded"),
407
+ "verdict_reason": parsed.get("reason") or rec.get("verdict_reason"),
408
+ }
409
+ gaps = [k for k in TIER2_FIELDS if ev.get(k) is None]
410
+ if gaps:
411
+ return STATUS_BLINDSPOT, None, {"reason": "missing_field:" + ",".join(gaps),
412
+ "source": src, "joined_by": joined_by}
413
+ return STATUS_WHITEBOX, ev, {"reason": None, "source": src,
414
+ "joined_by": joined_by}
415
+
416
+
417
+ # ---- 预演 / 执行 / 回滚 / 留痕 --------------------------------------------
418
+
419
+ # 生效条件:x 经 _as_cg 转换;prefixes 为假值回落 VISION_PREFIXES、aeis_root_ 为假值回落 aeis_root();ids 为真值时才按 set(ids) 过滤 nodes;对 nodes 调 build_evidence,locked 节点只累加 skipped_locked,白箱项入 items、其余入 blindspot_items,全程不写盘并返回含 aeis_root/sources/nodes_scanned/targeted/blindspot/by_reason 的报表。
420
+ def plan(x, layer=None, prefixes=None, limit=None,
421
+ aeis_root_=None, ids=None) -> dict:
422
+ """预演:产出证据回填清单,不写盘。"""
423
+ cg = _as_cg(x)
424
+ prefixes = tuple(prefixes) if prefixes else VISION_PREFIXES
425
+ root_used = aeis_root_ or aeis_root()
426
+ sources = load_sources(root_used)
427
+ nodes = _vision_nodes(cg, layer=layer, prefixes=prefixes, limit=limit)
428
+ if ids:
429
+ want = set(ids)
430
+ nodes = [n for n in nodes if n["id"] in want]
431
+ roots = _family_root_cond(_vision_nodes(cg, layer=layer,
432
+ prefixes=prefixes))
433
+ items, blind, locked = [], [], 0
434
+ for n in nodes:
435
+ status, ev, meta = build_evidence(n, sources, roots, root_used)
436
+ if n.get("locked"):
437
+ locked += 1
438
+ continue
439
+ row = {"id": n["id"], "layer": n.get("layer"), "status": status,
440
+ "type": (n.get("parsed") or {}).get("type"),
441
+ "reason": meta.get("reason"),
442
+ "joined_by": meta.get("joined_by"),
443
+ "source": _gallery_ref(meta["source"]["gallery"])
444
+ if meta.get("source") else None,
445
+ "already": n["fm"].get("evidence_status")}
446
+ if status == STATUS_WHITEBOX:
447
+ row["evidence"] = ev
448
+ items.append(row)
449
+ else:
450
+ blind.append(row)
451
+ by_reason = {}
452
+ for r in blind:
453
+ k = r.get("reason") or "?"
454
+ by_reason[k] = by_reason.get(k, 0) + 1
455
+ return {
456
+ "root": cg.root, "dry_run": True, "action": "vision_evidence",
457
+ "aeis_root": root_used, "authority_doc": AUTHORITY_DOC,
458
+ "sources": [{"gallery": _gallery_ref(s["gallery"]),
459
+ "image_id": s["image_id"],
460
+ "parts": len(s["by_type"])} for s in sources],
461
+ "nodes_scanned": len(nodes), "skipped_locked": locked,
462
+ "targeted": len(items), "blindspot": len(blind),
463
+ "blindspot_by_reason": by_reason,
464
+ "items": items, "blindspot_items": blind,
465
+ }
466
+
467
+
468
+ # 生效条件:x 经 _as_cg,batch 为假值回落 BATCH_DEFAULT 并交给 _unique_batch(cg, …) 去重;ids 为真值才按 id 过滤、entry_ids 为真值才按 _entry_id(batch,id) 过滤;循环中节点不在 cg.index["nodes"] 记 skipped_drift,fm 为 None 或内容加密记 skipped_locked,fm 已有同 status(白箱还要求 evidence 相同)记 skipped_already,否则改写 fm 并落盘、追加 jsonl、收集 entry_id;written 非 0 时 cg.rebuild_index() 并尝试 evolution.record(异常被吞)后返回 rep。
469
+ def apply(x, ids=None, entry_ids=None, layer=None, prefixes=None,
470
+ limit=None, batch=None, aeis_root_=None, actor=None) -> dict:
471
+ """执行回填:逐节点改写 frontmatter 证据面,写 `_vision_evidence.jsonl`。"""
472
+ cg = _as_cg(x)
473
+ p = plan(cg, layer=layer, prefixes=prefixes, limit=limit,
474
+ aeis_root_=aeis_root_)
475
+ batch = _unique_batch(cg, batch or BATCH_DEFAULT)
476
+ want_ids = set(ids) if ids else None
477
+ want_eids = set(entry_ids) if entry_ids else None
478
+ rows = list(p["items"]) + list(p["blindspot_items"])
479
+ if want_ids is not None:
480
+ rows = [r for r in rows if r["id"] in want_ids]
481
+ if want_eids is not None:
482
+ rows = [r for r in rows
483
+ if _entry_id(batch, r["id"]) in want_eids]
484
+ rep = {"root": cg.root, "dry_run": False, "action": "vision_evidence",
485
+ "batch": batch, "actor": actor, "aeis_root": p["aeis_root"],
486
+ "authority_doc": AUTHORITY_DOC,
487
+ "planned": len(rows), "written": 0, "blindspot_written": 0,
488
+ "skipped_locked": p["skipped_locked"],
489
+ "skipped_already": 0, "skipped_drift": 0, "entry_ids": []}
490
+ for r in rows:
491
+ nid = r["id"]
492
+ e = cg.index["nodes"].get(nid)
493
+ if not e:
494
+ rep["skipped_drift"] += 1
495
+ continue
496
+ fm, content = direct_read(cg, e) # 写前重查走盘上真值(读缓存口径)
497
+ if fm is None or crypto.is_encrypted(content):
498
+ rep["skipped_locked"] += 1
499
+ continue
500
+ status = r["status"]
501
+ ev = r.get("evidence")
502
+ if fm.get("evidence_status") == status and \
503
+ (status == STATUS_BLINDSPOT or fm.get("evidence") == ev):
504
+ rep["skipped_already"] += 1
505
+ continue
506
+ fm.pop("evidence", None)
507
+ if status == STATUS_WHITEBOX:
508
+ fm["evidence"] = ev
509
+ fm["evidence_status"] = STATUS_WHITEBOX
510
+ fm["evidence_tier"] = 2
511
+ fm["evidence_joined_by"] = r.get("joined_by")
512
+ fm["evidence_source"] = ("aeis:vision:%s" % r["source"]
513
+ if r.get("source") else None)
514
+ rep["written"] += 1
515
+ else:
516
+ fm["evidence_status"] = STATUS_BLINDSPOT
517
+ fm["evidence_blindspot_reason"] = r.get("reason")
518
+ rep["blindspot_written"] += 1
519
+ rep["written"] += 1
520
+ fm["evidence_doc"] = AUTHORITY_DOC
521
+ fm["evidence_batch"] = batch
522
+ fm["evidence_at"] = time.time()
523
+ cg._write_node(nid, os.path.join(cg.root, e["path"]), fm, content,
524
+ durable=True)
525
+ append_jsonl(_log_path(cg), {
526
+ "action": "vision_evidence", "ts": time.time(), "batch": batch,
527
+ "actor": actor, "entry_id": _entry_id(batch, nid), "node": nid,
528
+ "layer": e.get("layer"), "status": status, "tier": 2
529
+ if status == STATUS_WHITEBOX else 3,
530
+ "evidence": ev, "reason": r.get("reason"),
531
+ "source": r.get("source"), "joined_by": r.get("joined_by")})
532
+ rep["entry_ids"].append(_entry_id(batch, nid))
533
+ if rep["written"]:
534
+ cg.rebuild_index()
535
+ try:
536
+ evolution.record(
537
+ cg, kind=evolution.KIND_GENERAL,
538
+ pattern=("视觉证据面缺口的闭合方式:以只读归档逐部件结构化结果"
539
+ "(cond_hash 连接)反填节点证据,脱敏为图集编号引用"),
540
+ action="vision_evidence",
541
+ evidence=("batch=%s written=%d whitebox=%d blindspot=%d"
542
+ % (batch, rep["written"], len(p["items"]),
543
+ rep["blindspot_written"])),
544
+ source="data/vision(只读,本仓)",
545
+ extra={"batch": batch, "authority_doc": AUTHORITY_DOC})
546
+ except Exception: # noqa: BLE001
547
+ pass # 留痕失败不拖垮批次
548
+ rep["blindspot"] = len(p["blindspot_items"])
549
+ return rep
550
+
551
+
552
+ # 生效条件:x 经 _as_cg 后读 _log_path(cg) 日志,只处理 action=="vision_evidence" 记录;batch 为真值时仅取 batch 相同记录、entry_ids 为真值时仅取 entry_id 在集合内记录、该 entry_id 已出现在 rollback 日志则记 skipped_done;节点缺失或 _read 返回 fm 为 None 记 missing,EVIDENCE_KEYS 一个都不在 fm 中记 skipped_done,否则删除命中键、写盘并追加 rollback 留痕,reverted 非 0 时重建索引后返回 rep。
553
+ def rollback(x, batch=None, entry_ids=None, actor=None) -> dict:
554
+ """按留痕反向应用:删除本批次写入的证据键(幂等,防覆盖)。"""
555
+ cg = _as_cg(x)
556
+ want = set(entry_ids) if entry_ids else None
557
+ rep = {"root": cg.root, "action": "vision_evidence_rollback",
558
+ "actor": actor, "batch": batch, "reverted": 0, "missing": 0,
559
+ "skipped_done": 0, "cleared_keys": 0}
560
+ log = list(read_jsonl(_log_path(cg)) or [])
561
+ done = {r.get("entry_id") for r in log
562
+ if r.get("action") == "vision_evidence_rollback"
563
+ and r.get("entry_id")}
564
+ for rec in log:
565
+ if rec.get("action") != "vision_evidence":
566
+ continue
567
+ if batch and rec.get("batch") != batch:
568
+ continue
569
+ eid = rec.get("entry_id")
570
+ if want is not None and eid not in want:
571
+ continue
572
+ if eid in done:
573
+ rep["skipped_done"] += 1
574
+ continue
575
+ nid = rec.get("node")
576
+ e = cg.index["nodes"].get(nid)
577
+ if not e:
578
+ rep["missing"] += 1
579
+ continue
580
+ fm, content = direct_read(cg, e) # 回滚比对走盘上真值(读缓存口径)
581
+ if fm is None:
582
+ rep["missing"] += 1
583
+ continue
584
+ cleared = 0
585
+ for k in EVIDENCE_KEYS:
586
+ if k in fm:
587
+ fm.pop(k, None)
588
+ cleared += 1
589
+ if not cleared:
590
+ rep["skipped_done"] += 1
591
+ continue
592
+ cg._write_node(nid, os.path.join(cg.root, e["path"]), fm, content,
593
+ durable=True)
594
+ append_jsonl(_log_path(cg), {
595
+ "action": "vision_evidence_rollback", "ts": time.time(),
596
+ "actor": actor, "batch": rec.get("batch"), "entry_id": eid,
597
+ "node": nid, "cleared_keys": cleared})
598
+ rep["reverted"] += 1
599
+ rep["cleared_keys"] += cleared
600
+ if rep["reverted"]:
601
+ cg.rebuild_index()
602
+ return rep
603
+
604
+
605
+ # 生效条件:x 经 _as_cg 后逐条读 _log_path(cg),batch 为真值时才按 rec.get("batch")==batch 过滤;limit 非 None 且 >=0 时执行 recs = recs[-limit:](limit=0 因 -0 切片退化为全量),limit 为 None 或负数时不截断,返回 {root,total,returned,records}。
606
+ def history(x, limit=100, batch=None) -> dict:
607
+ cg = _as_cg(x)
608
+ recs = []
609
+ for rec in read_jsonl(_log_path(cg)) or []:
610
+ if batch and rec.get("batch") != batch:
611
+ continue
612
+ recs.append(rec)
613
+ total = len(recs)
614
+ if limit is not None and limit >= 0:
615
+ recs = recs[-limit:]
616
+ return {"root": cg.root, "total": total, "returned": len(recs),
617
+ "records": recs}
618
+
619
+
620
+ # ---- CLI(真实库预演/执行用;MCP 侧走 maintain action) -------------------
621
+
622
+ # 生效条件:argv 为 None 时 argparse 取 sys.argv;--prefixes 默认由 ",".join(VISION_PREFIXES) 提供并切出非空前缀;a.rollback 为真调 rollback(entry_ids 切分后为空则传 None)、否则 a.apply 为真调 apply、否则调 plan;--json 为真打印整份 JSON,否则按固定关键字打印并恒返回 0。
623
+ def _main(argv=None) -> int:
624
+ import argparse
625
+ ap = argparse.ArgumentParser(description="G5 视觉证据回填(默认只预演)")
626
+ ap.add_argument("root", help="认知图库根")
627
+ ap.add_argument("--aeis-root", default=None, help="AEIS 仓库根(只读证据源)")
628
+ ap.add_argument("--layer", default=None, help="限定层;缺省全层(按前缀)")
629
+ ap.add_argument("--prefixes", default=",".join(VISION_PREFIXES))
630
+ ap.add_argument("--limit", type=int, default=None)
631
+ ap.add_argument("--batch", default=None)
632
+ ap.add_argument("--actor", default="maintain")
633
+ ap.add_argument("--apply", action="store_true", help="真正写盘(默认预演)")
634
+ ap.add_argument("--rollback", action="store_true", help="按批次/定向回滚")
635
+ ap.add_argument("--entry-ids", default=None)
636
+ ap.add_argument("--json", action="store_true", help="输出完整 JSON 报表")
637
+ a = ap.parse_args(argv)
638
+ prefixes = [p for p in (a.prefixes or "").split(",") if p]
639
+ if a.rollback:
640
+ out = rollback(a.root, batch=a.batch,
641
+ entry_ids=[x for x in (a.entry_ids or "").split(",") if x]
642
+ or None, actor=a.actor)
643
+ elif a.apply:
644
+ out = apply(a.root, layer=a.layer, prefixes=prefixes, limit=a.limit,
645
+ batch=a.batch, aeis_root_=a.aeis_root, actor=a.actor)
646
+ else:
647
+ out = plan(a.root, layer=a.layer, prefixes=prefixes, limit=a.limit,
648
+ aeis_root_=a.aeis_root)
649
+ if a.json:
650
+ print(json.dumps(out, ensure_ascii=False, indent=2))
651
+ else:
652
+ for k in ("root", "aeis_root", "nodes_scanned", "targeted", "blindspot",
653
+ "blindspot_by_reason", "written", "blindspot_written",
654
+ "skipped_locked", "skipped_already", "batch", "reverted"):
655
+ if k in out:
656
+ print("%-22s %s" % (k, out[k]))
657
+ for s in out.get("sources") or []:
658
+ print(" source %s image_id=%s parts=%s"
659
+ % (s["gallery"], s["image_id"], s["parts"]))
660
+ if out.get("items"):
661
+ print("targeted sample:",
662
+ [(i["id"], i["joined_by"], (i["evidence"] or {}).get("algo"))
663
+ for i in out["items"][:3]])
664
+ return 0
665
+
666
+
667
+ if __name__ == "__main__": # pragma: no cover
667
668
  raise SystemExit(_main())