@furongjun1999/dsh-memory 0.4.11 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (504) hide show
  1. package/README.md +16 -16
  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 +142 -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//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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. package/docs/hive//350/234/202/345/267/242/345/217/214/345/256/236/344/276/213/344/272/222/351/252/214_/350/256/276/350/256/241/345/256/232/347/250/277.md +503 -503
  24. 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
  25. 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
  26. 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
  27. 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
  28. package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +646 -646
  29. package/docs/mdcg/lingshu_tutorial.html +14449 -14449
  30. package/docs/mdcg/release_v0.4.11.md +49 -0
  31. package/docs/mdcg/release_v0.4.5.md +55 -55
  32. package/docs/mdcg/tool_table_v0.3.0.md +117 -117
  33. 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
  34. 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
  35. 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
  36. package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +54 -0
  37. 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
  38. 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
  39. 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
  40. package/docs/mdcg//347/274/272/345/217/243/345/215/225_P0/346/224/266/345/217/243_v0.1.md +270 -270
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. package/docs/swarm//350/234/202/347/276/244/344/272/222/350/201/224_v0.1.md +704 -704
  47. 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
  48. 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
  49. 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
  50. package/docs/theory//346/231/272/350/203/275/350/256/2723.4.md +5260 -5260
  51. 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
  52. 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
  53. 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
  54. 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
  55. 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
  56. 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
  57. 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
  58. package/dsh/README.md +82 -82
  59. package/dsh/cordis.yml.example +139 -139
  60. package/dsh/update-lingshu.bat +11 -11
  61. package/lib/hooks.js +36 -2
  62. package/lib/lib/roleplay_web.js +427 -427
  63. package/md_cg/__init__.py +7 -7
  64. package/md_cg/audit.py +368 -368
  65. package/md_cg/autonomy.py +287 -287
  66. package/md_cg/backfill.py +1327 -1327
  67. package/md_cg/backfill_bigdomain.py +34 -34
  68. package/md_cg/bench6_arms.py +410 -410
  69. package/md_cg/bench6_common.py +230 -230
  70. package/md_cg/bench6_competitors.py +212 -212
  71. package/md_cg/bench_axis_domain.py +257 -257
  72. package/md_cg/bench_blind_comp.py +308 -308
  73. package/md_cg/bench_en_atoms_public.py +230 -230
  74. package/md_cg/bench_governance.py +348 -348
  75. package/md_cg/bench_lme_zh.py +410 -410
  76. package/md_cg/bench_locomo.py +121 -121
  77. package/md_cg/bench_locomo_zh.py +450 -450
  78. package/md_cg/bench_locomo_zh_public.py +147 -147
  79. package/md_cg/bench_longmem.py +112 -112
  80. package/md_cg/bench_membench.py +632 -632
  81. package/md_cg/bench_p0.py +149 -149
  82. package/md_cg/bench_progressive.py +287 -287
  83. package/md_cg/bench_role_views.py +238 -238
  84. package/md_cg/bench_task_ab.py +243 -243
  85. package/md_cg/bench_task_ab_llm.py +408 -408
  86. package/md_cg/bench_unified_en.py +204 -204
  87. package/md_cg/bench_zh_mad.py +601 -601
  88. package/md_cg/blindspot_tickets.py +123 -123
  89. package/md_cg/branches.py +285 -285
  90. package/md_cg/build_postings.py +73 -73
  91. package/md_cg/ccgc.py +1005 -948
  92. package/md_cg/census.py +132 -132
  93. package/md_cg/chain.py +300 -300
  94. package/md_cg/codeindex.py +531 -531
  95. package/md_cg/coldverify.py +292 -292
  96. package/md_cg/comment_gate.py +337 -337
  97. package/md_cg/cond_compose.py +190 -190
  98. package/md_cg/cond_facts.py +154 -154
  99. package/md_cg/cond_template.json +106 -106
  100. package/md_cg/condition_anchor.py +142 -142
  101. package/md_cg/conformance.py +726 -726
  102. package/md_cg/consistency.py +717 -717
  103. package/md_cg/consolidate.py +1536 -1439
  104. package/md_cg/corpus.py +110 -110
  105. package/md_cg/crosscheck.py +1097 -1097
  106. package/md_cg/crypto.py +437 -437
  107. package/md_cg/d_meta.py +310 -310
  108. package/md_cg/datapath.py +334 -334
  109. package/md_cg/docindex.py +473 -473
  110. package/md_cg/eval_common.py +575 -575
  111. package/md_cg/evidence.py +580 -580
  112. package/md_cg/evolution.py +477 -477
  113. package/md_cg/export.py +220 -220
  114. package/md_cg/forgetting.py +581 -581
  115. package/md_cg/fsutil.py +329 -329
  116. package/md_cg/hotcache.py +238 -214
  117. package/md_cg/hyperedge.py +251 -251
  118. package/md_cg/identity.py +390 -390
  119. package/md_cg/insight.py +500 -500
  120. package/md_cg/interop.py +199 -0
  121. package/md_cg/lexicon/build_cedict_en_zh.py +329 -329
  122. package/md_cg/lexicon/build_standard_en.py +171 -171
  123. package/md_cg/lexicon/expand_en_zh.py +211 -211
  124. package/md_cg/lifecycle.py +272 -272
  125. package/md_cg/linkref.py +280 -280
  126. package/md_cg/links.py +622 -622
  127. package/md_cg/mcp_server.py +129 -32
  128. package/md_cg/md_whitebox.py +345 -345
  129. package/md_cg/mdcg.py +176 -117
  130. package/md_cg/mdcos.py +79 -13
  131. package/md_cg/metacognition.py +591 -591
  132. package/md_cg/migrate.py +119 -119
  133. package/md_cg/migrate_aeis.py +221 -221
  134. package/md_cg/migrate_roleplay.py +293 -293
  135. package/md_cg/migrate_wisdom_graph.py +360 -360
  136. package/md_cg/mreview/__init__.py +25 -25
  137. package/md_cg/mreview/__main__.py +110 -110
  138. package/md_cg/mreview/bundle.py +178 -178
  139. package/md_cg/mreview/candidates.py +262 -262
  140. package/md_cg/mreview/govern.py +693 -693
  141. package/md_cg/mreview/locate.py +939 -939
  142. package/md_cg/mreview/pipeline.py +728 -728
  143. package/md_cg/mreview/rules/duplication.json +21 -21
  144. package/md_cg/mreview/rules/field_coverage.json +54 -54
  145. package/md_cg/mreview/rules/source_license.json +21 -21
  146. package/md_cg/mreview/rules/template_flow.json +21 -21
  147. package/md_cg/mreview/ruleset.py +252 -252
  148. package/md_cg/nodefile.py +575 -575
  149. package/md_cg/pooling.py +484 -472
  150. package/md_cg/postings.py +298 -298
  151. package/md_cg/predict.py +1100 -1100
  152. package/md_cg/progressive.py +123 -123
  153. package/md_cg/protect.py +272 -272
  154. package/md_cg/protocol/md_cg_gate.proto +33 -33
  155. package/md_cg/protocol.py +372 -372
  156. package/md_cg/provenance.py +582 -582
  157. package/md_cg/reach.py +453 -453
  158. package/md_cg/readcache.py +85 -0
  159. package/md_cg/refindex.py +833 -833
  160. package/md_cg/refine.py +604 -604
  161. package/md_cg/roleviews.py +89 -89
  162. package/md_cg/routing.py +365 -365
  163. package/md_cg/scrub.py +852 -852
  164. package/md_cg/security.py +274 -274
  165. package/md_cg/self_state.py +1029 -1029
  166. package/md_cg/selfreport.py +151 -151
  167. package/md_cg/semantic/__init__.py +10 -10
  168. package/md_cg/semantic/canonical.py +122 -122
  169. package/md_cg/semantic/en_normalizer.py +364 -364
  170. package/md_cg/semantic/en_zh_map.json +28694 -0
  171. package/md_cg/semantic/export_en_zh_map.py +64 -0
  172. package/md_cg/semantic/unify.py +45 -0
  173. package/md_cg/semantic/zh_en_atoms.py +139 -139
  174. package/md_cg/signer.py +562 -562
  175. package/md_cg/sources.py +815 -582
  176. package/md_cg/statushdr.py +179 -179
  177. package/md_cg/stg.py +48 -37
  178. package/md_cg/subgraph.py +729 -729
  179. package/md_cg/sustain.py +1138 -1138
  180. package/md_cg/tasks.py +470 -470
  181. package/md_cg/test_action_derive.py +203 -203
  182. package/md_cg/test_audit_rotate.py +270 -270
  183. package/md_cg/test_autonomy.py +143 -143
  184. package/md_cg/test_bench_governance.py +102 -102
  185. package/md_cg/test_blindspot_tickets.py +166 -166
  186. package/md_cg/test_branches.py +249 -249
  187. package/md_cg/test_ccg_perturb.py +184 -184
  188. package/md_cg/test_ccgc.py +433 -433
  189. package/md_cg/test_census_prune.py +81 -81
  190. package/md_cg/test_cond_compose_anchors.py +76 -76
  191. package/md_cg/test_cond_match.py +165 -165
  192. package/md_cg/test_condition_anchor.py +81 -81
  193. package/md_cg/test_d_meta.py +412 -412
  194. package/md_cg/test_datapath_root.py +199 -199
  195. package/md_cg/test_en_pipeline.py +166 -166
  196. package/md_cg/test_gain_gate.py +212 -212
  197. package/md_cg/test_health_scale.py +173 -173
  198. package/md_cg/test_hive_ingest.py +285 -0
  199. package/md_cg/test_hot_cold.py +215 -215
  200. package/md_cg/test_hyperedge.py +245 -245
  201. package/md_cg/test_i26_empty_first_write.py +116 -0
  202. package/md_cg/test_i27_e041_identity.py +128 -0
  203. package/md_cg/test_i28_hotcache_prodpath.py +122 -0
  204. package/md_cg/test_identity_attribution.py +147 -147
  205. package/md_cg/test_index_durability.py +224 -224
  206. package/md_cg/test_interop.py +93 -0
  207. package/md_cg/test_lifecycle.py +309 -309
  208. package/md_cg/test_linkref.py +306 -306
  209. package/md_cg/test_lock.py +43 -43
  210. package/md_cg/test_md_access_parity.py +255 -255
  211. package/md_cg/test_md_writepath.py +345 -345
  212. package/md_cg/test_mdstore_search_parity.py +160 -0
  213. package/md_cg/test_mr_m2.py +587 -587
  214. package/md_cg/test_mr_m3.py +710 -710
  215. package/md_cg/test_mr_m4.py +485 -485
  216. package/md_cg/test_p0.py +250 -250
  217. package/md_cg/test_p1.py +316 -316
  218. package/md_cg/test_p10_identity.py +173 -173
  219. package/md_cg/test_p11_consistency.py +233 -233
  220. package/md_cg/test_p12_metacognition.py +212 -212
  221. package/md_cg/test_p13_encryption.py +241 -241
  222. package/md_cg/test_p14_sustain.py +249 -249
  223. package/md_cg/test_p15_scrub.py +280 -280
  224. package/md_cg/test_p16_self_state.py +301 -301
  225. package/md_cg/test_p17_predict.py +354 -354
  226. package/md_cg/test_p18_whitebox.py +171 -171
  227. package/md_cg/test_p19_migrate_roleplay.py +149 -149
  228. package/md_cg/test_p20_evolution.py +315 -315
  229. package/md_cg/test_p21_tokens.py +293 -270
  230. package/md_cg/test_p22_theory.py +175 -175
  231. package/md_cg/test_p23_links.py +311 -311
  232. package/md_cg/test_p24_evidence.py +227 -227
  233. package/md_cg/test_p25_weights.py +156 -156
  234. package/md_cg/test_p26_refindex.py +416 -416
  235. package/md_cg/test_p27_docindex.py +765 -765
  236. package/md_cg/test_p28_refcheck.py +305 -305
  237. package/md_cg/test_p29_session_ingest_export.py +354 -333
  238. package/md_cg/test_p3.py +11 -2
  239. package/md_cg/test_p30_maintain.py +330 -330
  240. package/md_cg/test_p31_insight.py +534 -534
  241. package/md_cg/test_p32_backfill.py +298 -298
  242. package/md_cg/test_p33_ccg_wiring.py +293 -293
  243. package/md_cg/test_p34_crosscheck.py +331 -331
  244. package/md_cg/test_p35_conditioned_claim.py +252 -252
  245. package/md_cg/test_p36_kp_align.py +230 -230
  246. package/md_cg/test_p37_condition_space.py +248 -248
  247. package/md_cg/test_p38_concurrent_flush.py +102 -0
  248. package/md_cg/test_p38_contextualize.py +273 -273
  249. package/md_cg/test_p39_verify_flow.py +113 -0
  250. package/md_cg/test_p39_vision_evidence.py +369 -369
  251. package/md_cg/test_p40_refine_worklist.py +241 -241
  252. package/md_cg/test_p41_evolve_patrol.py +224 -224
  253. package/md_cg/test_p42_provenance.py +269 -269
  254. package/md_cg/test_p43_pooling.py +412 -398
  255. package/md_cg/test_p44_md_whitebox.py +231 -231
  256. package/md_cg/test_p45_session_identity.py +219 -219
  257. package/md_cg/test_p46_unit_scope.py +272 -272
  258. package/md_cg/test_p47_session_view.py +281 -0
  259. package/md_cg/test_p4_fuzzy.py +223 -223
  260. package/md_cg/test_p5_semantic.py +226 -226
  261. package/md_cg/test_p6_consolidate.py +440 -387
  262. package/md_cg/test_p7_goals_recent.py +202 -202
  263. package/md_cg/test_p8_subgraph_chain.py +200 -200
  264. package/md_cg/test_p9_forget_protect.py +231 -231
  265. package/md_cg/test_predict_beta.py +135 -135
  266. package/md_cg/test_preflight_failclosed.py +100 -100
  267. package/md_cg/test_progressive.py +146 -146
  268. package/md_cg/test_protocol.py +243 -243
  269. package/md_cg/test_reach.py +378 -378
  270. package/md_cg/test_reach_keys.py +201 -201
  271. package/md_cg/test_read_clip.py +141 -141
  272. package/md_cg/test_readcache_prodpath.py +155 -0
  273. package/md_cg/test_retr_gates_prodpath.py +140 -0
  274. package/md_cg/test_retr_s1.py +340 -340
  275. package/md_cg/test_retr_s1b.py +209 -209
  276. package/md_cg/test_retr_s3.py +194 -194
  277. package/md_cg/test_retr_s4.py +163 -163
  278. package/md_cg/test_retr_s5.py +200 -200
  279. package/md_cg/test_retr_s6.py +157 -157
  280. package/md_cg/test_retr_s7.py +384 -384
  281. package/md_cg/test_retr_s8_time.py +369 -316
  282. package/md_cg/test_retr_s9_edges.py +286 -286
  283. package/md_cg/test_retr_s9_entity_ctx.py +175 -175
  284. package/md_cg/test_review_conformance.py +367 -367
  285. package/md_cg/test_role_views.py +354 -354
  286. package/md_cg/test_sem_noise.py +242 -242
  287. package/md_cg/test_semantic_canonical.py +241 -241
  288. package/md_cg/test_subproc_encoding.py +192 -192
  289. package/md_cg/test_sustain_mutual.py +153 -153
  290. package/md_cg/test_tasks.py +409 -409
  291. package/md_cg/test_tool_face.py +189 -189
  292. package/md_cg/test_transfer.py +180 -180
  293. package/md_cg/test_trust.py +361 -361
  294. package/md_cg/test_twophase.py +286 -286
  295. package/md_cg/test_v14_fixes.py +397 -397
  296. package/md_cg/test_validity_filter.py +280 -280
  297. package/md_cg/test_verify_answer.py +138 -138
  298. package/md_cg/test_wisdom_md_store.py +292 -292
  299. package/md_cg/test_writelimit.py +197 -197
  300. package/md_cg/test_writepipe.py +214 -214
  301. package/md_cg/theory.py +273 -273
  302. package/md_cg/tokens.py +677 -663
  303. package/md_cg/tool_face.py +260 -260
  304. package/md_cg/trust.py +986 -950
  305. package/md_cg/twophase.py +231 -231
  306. package/md_cg/units.py +667 -667
  307. package/md_cg/vision_evidence.py +666 -666
  308. package/md_cg/weights.py +624 -624
  309. package/md_cg/whitebox.py +527 -527
  310. package/md_cg/whitebox_kb/__init__.py +37 -37
  311. package/md_cg/whitebox_kb/aeis_core/__init__.py +42 -42
  312. package/md_cg/whitebox_kb/aeis_core/semantic.py +280 -280
  313. package/md_cg/whitebox_kb/aeis_core/textutil.py +13 -13
  314. package/md_cg/whitebox_kb/engine.py +310 -310
  315. package/md_cg/whitebox_kb/seed_knowledge//346/231/272/350/203/275/350/256/2723.4.md +5260 -5260
  316. package/md_cg/whitebox_kb/wisdom/browser_units.py +2631 -2631
  317. package/md_cg/whitebox_kb/wisdom/causal_discover.py +432 -432
  318. package/md_cg/whitebox_kb/wisdom/chat_engine.py +1506 -1506
  319. package/md_cg/whitebox_kb/wisdom/code_solidified.json +6195 -6195
  320. package/md_cg/whitebox_kb/wisdom/compiler_code_units.py +3033 -3033
  321. package/md_cg/whitebox_kb/wisdom/condition_algebra.py +112 -112
  322. package/md_cg/whitebox_kb/wisdom/condition_frame.py +315 -315
  323. package/md_cg/whitebox_kb/wisdom/condition_kb.py +108 -108
  324. package/md_cg/whitebox_kb/wisdom/conflict_map.json +4445 -4445
  325. package/md_cg/whitebox_kb/wisdom/core/lexer.py +512 -512
  326. package/md_cg/whitebox_kb/wisdom/core/name_checker.py +1023 -1023
  327. package/md_cg/whitebox_kb/wisdom/cspmn.py +258 -258
  328. package/md_cg/whitebox_kb/wisdom/csre.py +264 -264
  329. package/md_cg/whitebox_kb/wisdom/danmaku_audit.py +252 -252
  330. package/md_cg/whitebox_kb/wisdom/distilled_condition_units.json +6417 -6417
  331. package/md_cg/whitebox_kb/wisdom/docs/WB-EVAL-20260902b.json +1950 -1950
  332. package/md_cg/whitebox_kb/wisdom/docs/WB-EVAL-20260902c.json +1296 -1296
  333. package/md_cg/whitebox_kb/wisdom/docs/whitebox_capability_graph_demo.json +59 -59
  334. package/md_cg/whitebox_kb/wisdom/graph_db_units.py +3089 -3089
  335. package/md_cg/whitebox_kb/wisdom/knowledge_points.py +318 -318
  336. package/md_cg/whitebox_kb/wisdom/md_access.py +470 -470
  337. package/md_cg/whitebox_kb/wisdom/md_store.py +276 -251
  338. package/md_cg/whitebox_kb/wisdom/migrate_wisdom.py +476 -476
  339. package/md_cg/whitebox_kb/wisdom/navigate.py +248 -248
  340. package/md_cg/whitebox_kb/wisdom/neural_retrieve.py +224 -224
  341. package/md_cg/whitebox_kb/wisdom/os_units.py +2735 -2735
  342. package/md_cg/whitebox_kb/wisdom/pattern_separation.py +376 -376
  343. package/md_cg/whitebox_kb/wisdom/prereq_map.json +364 -364
  344. package/md_cg/whitebox_kb/wisdom/python_code_units.py +2766 -2766
  345. package/md_cg/whitebox_kb/wisdom/role_solidified.json +7 -7
  346. package/md_cg/whitebox_kb/wisdom/route_memory.py +244 -244
  347. package/md_cg/whitebox_kb/wisdom/scene_reconstruction.py +148 -148
  348. package/md_cg/whitebox_kb/wisdom/snr_report.json +40 -40
  349. package/md_cg/whitebox_kb/wisdom/test_code_compose_domains.py +7541 -7541
  350. package/md_cg/whitebox_kb/wisdom/test_compiler_self_bootstrap.py +354 -354
  351. package/md_cg/whitebox_kb/wisdom/test_ecosystem_assembly.py +158 -158
  352. package/md_cg/whitebox_kb/wisdom/test_ecosystem_demos.py +193 -193
  353. package/md_cg/whitebox_kb/wisdom/test_graph_db.py +292 -292
  354. package/md_cg/whitebox_kb/wisdom/test_python_self_bootstrap.py +160 -160
  355. package/md_cg/whitebox_kb/wisdom/trigger_words_index.json +4366 -4366
  356. package/md_cg/whitebox_kb/wisdom/verifier.py +1297 -1297
  357. package/md_cg/writelimit.py +356 -356
  358. package/md_cg/writepipe.py +550 -542
  359. package/package.json +97 -96
  360. package/skills/plugin.json +54 -54
  361. package/skills/skills/designer-perspective/SKILL.md +158 -158
  362. package/skills/skills/designer-perspective/references/01-observation-position.md +66 -66
  363. package/skills/skills/designer-perspective/references/02-structure-recognition.md +62 -62
  364. package/skills/skills/designer-perspective/references/03-direction-judgment.md +55 -55
  365. package/skills/skills/designer-perspective/references/04-qualification-verdict.md +72 -72
  366. package/skills/skills/designer-perspective/references/05-condition-attribution.md +74 -74
  367. package/skills/skills/designer-perspective/scripts/designer.py +545 -545
  368. package/skills/skills/designer-perspective/tests/cases.jsonl +17 -17
  369. package/skills/skills/designer-perspective/tests/selftest.py +61 -61
  370. package/skills/skills/lingshu-browser/SKILL.md +60 -60
  371. package/skills/skills/lingshu-compiler/SKILL.md +56 -56
  372. package/skills/skills/lingshu-compiler/units/analyze-type-infer/SKILL.md +45 -45
  373. package/skills/skills/lingshu-compiler/units/check-name-real/SKILL.md +45 -45
  374. package/skills/skills/lingshu-compiler/units/compile-assign/SKILL.md +45 -45
  375. package/skills/skills/lingshu-compiler/units/compile-expr-tree/SKILL.md +45 -45
  376. package/skills/skills/lingshu-compiler/units/compile-full-pipeline/SKILL.md +45 -45
  377. package/skills/skills/lingshu-compiler/units/compile-func-def/SKILL.md +45 -45
  378. package/skills/skills/lingshu-compiler/units/compile-if-then/SKILL.md +45 -45
  379. package/skills/skills/lingshu-compiler/units/compile-logic-expr/SKILL.md +45 -45
  380. package/skills/skills/lingshu-compiler/units/compile-recursive/SKILL.md +45 -45
  381. package/skills/skills/lingshu-compiler/units/compile-scope/SKILL.md +45 -45
  382. package/skills/skills/lingshu-compiler/units/compile-type-check/SKILL.md +45 -45
  383. package/skills/skills/lingshu-compiler/units/compile-while/SKILL.md +45 -45
  384. package/skills/skills/lingshu-compiler/units/compiler-0010c4bf/SKILL.md +45 -45
  385. package/skills/skills/lingshu-compiler/units/compiler-0355bffb/SKILL.md +45 -45
  386. package/skills/skills/lingshu-compiler/units/compiler-0361708a/SKILL.md +45 -45
  387. package/skills/skills/lingshu-compiler/units/compiler-054a0414/SKILL.md +45 -45
  388. package/skills/skills/lingshu-compiler/units/compiler-05a1691a/SKILL.md +45 -45
  389. package/skills/skills/lingshu-compiler/units/compiler-05eeed1e/SKILL.md +45 -45
  390. package/skills/skills/lingshu-compiler/units/compiler-0622a1f6/SKILL.md +45 -45
  391. package/skills/skills/lingshu-compiler/units/compiler-08b54217/SKILL.md +45 -45
  392. package/skills/skills/lingshu-compiler/units/compiler-0a62b70c/SKILL.md +45 -45
  393. package/skills/skills/lingshu-compiler/units/compiler-0ab24d00/SKILL.md +45 -45
  394. package/skills/skills/lingshu-compiler/units/compiler-0e093688/SKILL.md +45 -45
  395. package/skills/skills/lingshu-compiler/units/compiler-0e82b966/SKILL.md +45 -45
  396. package/skills/skills/lingshu-compiler/units/compiler-0ee9b9b9/SKILL.md +45 -45
  397. package/skills/skills/lingshu-compiler/units/compiler-0f3787e6/SKILL.md +45 -45
  398. package/skills/skills/lingshu-compiler/units/compiler-1028685f/SKILL.md +45 -45
  399. package/skills/skills/lingshu-compiler/units/compiler-11897630/SKILL.md +45 -45
  400. package/skills/skills/lingshu-compiler/units/compiler-16661b9b/SKILL.md +45 -45
  401. package/skills/skills/lingshu-compiler/units/compiler-1f722303/SKILL.md +45 -45
  402. package/skills/skills/lingshu-compiler/units/compiler-25be1262/SKILL.md +45 -45
  403. package/skills/skills/lingshu-compiler/units/compiler-2a76ba07/SKILL.md +45 -45
  404. package/skills/skills/lingshu-compiler/units/compiler-2dbea54a/SKILL.md +45 -45
  405. package/skills/skills/lingshu-compiler/units/compiler-2df52f16/SKILL.md +45 -45
  406. package/skills/skills/lingshu-compiler/units/compiler-2ec2c9d2/SKILL.md +45 -45
  407. package/skills/skills/lingshu-compiler/units/compiler-2f8c8f39/SKILL.md +45 -45
  408. package/skills/skills/lingshu-compiler/units/compiler-38378ac7/SKILL.md +45 -45
  409. package/skills/skills/lingshu-compiler/units/compiler-39457d2e/SKILL.md +45 -45
  410. package/skills/skills/lingshu-compiler/units/compiler-39fb5926/SKILL.md +45 -45
  411. package/skills/skills/lingshu-compiler/units/compiler-47f4fbfa/SKILL.md +45 -45
  412. package/skills/skills/lingshu-compiler/units/compiler-4a8cd1f1/SKILL.md +45 -45
  413. package/skills/skills/lingshu-compiler/units/compiler-4b230b6b/SKILL.md +45 -45
  414. package/skills/skills/lingshu-compiler/units/compiler-4cbbda95/SKILL.md +45 -45
  415. package/skills/skills/lingshu-compiler/units/compiler-4d5a68ff/SKILL.md +45 -45
  416. package/skills/skills/lingshu-compiler/units/compiler-56dc9bdd/SKILL.md +45 -45
  417. package/skills/skills/lingshu-compiler/units/compiler-57e76ebe/SKILL.md +45 -45
  418. package/skills/skills/lingshu-compiler/units/compiler-61ff016b/SKILL.md +45 -45
  419. package/skills/skills/lingshu-compiler/units/compiler-63eae588/SKILL.md +45 -45
  420. package/skills/skills/lingshu-compiler/units/compiler-64c4224b/SKILL.md +45 -45
  421. package/skills/skills/lingshu-compiler/units/compiler-66377955/SKILL.md +45 -45
  422. package/skills/skills/lingshu-compiler/units/compiler-674ab4f6/SKILL.md +45 -45
  423. package/skills/skills/lingshu-compiler/units/compiler-6af76fd6/SKILL.md +45 -45
  424. package/skills/skills/lingshu-compiler/units/compiler-6d979db2/SKILL.md +45 -45
  425. package/skills/skills/lingshu-compiler/units/compiler-6de326c0/SKILL.md +45 -45
  426. package/skills/skills/lingshu-compiler/units/compiler-6f1c0eea/SKILL.md +45 -45
  427. package/skills/skills/lingshu-compiler/units/compiler-724d6c9b/SKILL.md +45 -45
  428. package/skills/skills/lingshu-compiler/units/compiler-7690177f/SKILL.md +45 -45
  429. package/skills/skills/lingshu-compiler/units/compiler-7b229a7d/SKILL.md +45 -45
  430. package/skills/skills/lingshu-compiler/units/compiler-7f471b3a/SKILL.md +45 -45
  431. package/skills/skills/lingshu-compiler/units/compiler-815cad08/SKILL.md +45 -45
  432. package/skills/skills/lingshu-compiler/units/compiler-83c61634/SKILL.md +45 -45
  433. package/skills/skills/lingshu-compiler/units/compiler-8c798c81/SKILL.md +45 -45
  434. package/skills/skills/lingshu-compiler/units/compiler-8dd747ac/SKILL.md +45 -45
  435. package/skills/skills/lingshu-compiler/units/compiler-90324d0a/SKILL.md +45 -45
  436. package/skills/skills/lingshu-compiler/units/compiler-94b8d72d/SKILL.md +45 -45
  437. package/skills/skills/lingshu-compiler/units/compiler-94f12231/SKILL.md +45 -45
  438. package/skills/skills/lingshu-compiler/units/compiler-95937c16/SKILL.md +45 -45
  439. package/skills/skills/lingshu-compiler/units/compiler-98a5625b/SKILL.md +45 -45
  440. package/skills/skills/lingshu-compiler/units/compiler-98b4c42f/SKILL.md +45 -45
  441. package/skills/skills/lingshu-compiler/units/compiler-98e3894a/SKILL.md +45 -45
  442. package/skills/skills/lingshu-compiler/units/compiler-9bdfe4b8/SKILL.md +45 -45
  443. package/skills/skills/lingshu-compiler/units/compiler-9d9b4e83/SKILL.md +45 -45
  444. package/skills/skills/lingshu-compiler/units/compiler-9f7be5ad/SKILL.md +45 -45
  445. package/skills/skills/lingshu-compiler/units/compiler-a6daf076/SKILL.md +45 -45
  446. package/skills/skills/lingshu-compiler/units/compiler-a7995a0c/SKILL.md +45 -45
  447. package/skills/skills/lingshu-compiler/units/compiler-a8399248/SKILL.md +45 -45
  448. package/skills/skills/lingshu-compiler/units/compiler-aabbd099/SKILL.md +45 -45
  449. package/skills/skills/lingshu-compiler/units/compiler-afe169d8/SKILL.md +45 -45
  450. package/skills/skills/lingshu-compiler/units/compiler-b0678bda/SKILL.md +45 -45
  451. package/skills/skills/lingshu-compiler/units/compiler-b09bd196/SKILL.md +45 -45
  452. package/skills/skills/lingshu-compiler/units/compiler-b1396e23/SKILL.md +45 -45
  453. package/skills/skills/lingshu-compiler/units/compiler-b9b31ce0/SKILL.md +45 -45
  454. package/skills/skills/lingshu-compiler/units/compiler-c10264a7/SKILL.md +45 -45
  455. package/skills/skills/lingshu-compiler/units/compiler-cb1e8e4b/SKILL.md +45 -45
  456. package/skills/skills/lingshu-compiler/units/compiler-ccafd438/SKILL.md +45 -45
  457. package/skills/skills/lingshu-compiler/units/compiler-cda9c262/SKILL.md +45 -45
  458. package/skills/skills/lingshu-compiler/units/compiler-ce648068/SKILL.md +45 -45
  459. package/skills/skills/lingshu-compiler/units/compiler-cf5776a4/SKILL.md +45 -45
  460. package/skills/skills/lingshu-compiler/units/compiler-d974e5d3/SKILL.md +45 -45
  461. package/skills/skills/lingshu-compiler/units/compiler-e3979fd3/SKILL.md +45 -45
  462. package/skills/skills/lingshu-compiler/units/compiler-eb1cf2b5/SKILL.md +45 -45
  463. package/skills/skills/lingshu-compiler/units/compiler-ecb30d5b/SKILL.md +45 -45
  464. package/skills/skills/lingshu-compiler/units/compiler-f8c8b24b/SKILL.md +45 -45
  465. package/skills/skills/lingshu-compiler/units/compiler-f99fedbe/SKILL.md +45 -45
  466. package/skills/skills/lingshu-compiler/units/compiler-fa8ff5f7/SKILL.md +45 -45
  467. package/skills/skills/lingshu-compiler/units/compiler-fe1b058d/SKILL.md +45 -45
  468. package/skills/skills/lingshu-compiler/units/lex-chinese-program/SKILL.md +45 -45
  469. package/skills/skills/lingshu-compiler/units/lex-dao-de-jing/SKILL.md +45 -45
  470. package/skills/skills/lingshu-compiler/units/lex-nine-chapters/SKILL.md +45 -45
  471. package/skills/skills/lingshu-compiler/units/vm-arithmetic/SKILL.md +45 -45
  472. package/skills/skills/lingshu-compiler/units/vm-array-ops/SKILL.md +45 -45
  473. package/skills/skills/lingshu-compiler/units/vm-closure-call/SKILL.md +45 -45
  474. package/skills/skills/lingshu-compiler/units/vm-closure-create/SKILL.md +45 -45
  475. package/skills/skills/lingshu-compiler/units/vm-compare/SKILL.md +45 -45
  476. package/skills/skills/lingshu-compiler/units/vm-cond-jump/SKILL.md +45 -45
  477. package/skills/skills/lingshu-compiler/units/vm-cond-space/SKILL.md +45 -45
  478. package/skills/skills/lingshu-compiler/units/vm-exception/SKILL.md +45 -45
  479. package/skills/skills/lingshu-compiler/units/vm-func-call/SKILL.md +45 -45
  480. package/skills/skills/lingshu-compiler/units/vm-loop-run/SKILL.md +45 -45
  481. package/skills/skills/lingshu-compiler/units/vm-profiling/SKILL.md +45 -45
  482. package/skills/skills/lingshu-compiler/units/vm-refcount/SKILL.md +45 -45
  483. package/skills/skills/lingshu-compiler/units/vm-run-loop/SKILL.md +45 -45
  484. package/skills/skills/lingshu-compiler/units/vm-short-circuit/SKILL.md +45 -45
  485. package/skills/skills/lingshu-compiler/units/vm-stack-guard/SKILL.md +45 -45
  486. package/skills/skills/lingshu-compiler/units/vm-stack-ops/SKILL.md +45 -45
  487. package/skills/skills/lingshu-compiler/units/vm-trust-accum/SKILL.md +45 -45
  488. package/skills/skills/lingshu-graph/SKILL.md +63 -63
  489. package/skills/skills/lingshu-net/SKILL.md +48 -48
  490. package/skills/skills/lingshu-os/SKILL.md +64 -64
  491. package/skills/skills/lingshu-pylang/SKILL.md +71 -71
  492. package/src/bridge.ts +401 -401
  493. package/src/hooks.ts +38 -2
  494. package/src/lib/datapath.ts +326 -326
  495. package/src/lib/mdcg_client.ts +413 -413
  496. package/src/lib/mutual.ts +428 -428
  497. package/src/lib/prompt_safety.ts +62 -62
  498. package/src/lib/python_path.ts +71 -71
  499. package/src/lib/roleplay_web.ts +932 -932
  500. package/src/lib/token_store.ts +192 -192
  501. package/src/tools.ts +212 -212
  502. package/zcode/AGENTS.md +11 -3
  503. package/zcode/README.md +41 -41
  504. /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,667 @@
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
+
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
667
667
  raise SystemExit(_main())