@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.
- package/README.md +16 -16
- package/codebuddy/CODEBUDDY.md +11 -3
- package/codebuddy/README.md +92 -90
- package/codebuddy/mcp.json +27 -27
- 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
- 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
- package/docs/README.md +142 -111
- package/docs/discipline/harnesses.yaml +244 -226
- package/docs/discipline/templates/full.md.tmpl +61 -61
- package/docs/discipline/templates/rules.mdc.tmpl +68 -0
- package/docs/discipline/templates/skill.md.tmpl +23 -23
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +646 -646
- package/docs/mdcg/lingshu_tutorial.html +14449 -14449
- package/docs/mdcg/release_v0.4.11.md +49 -0
- package/docs/mdcg/release_v0.4.5.md +55 -55
- package/docs/mdcg/tool_table_v0.3.0.md +117 -117
- 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
- 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
- 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
- package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +54 -0
- 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
- 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
- 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
- package/docs/mdcg//347/274/272/345/217/243/345/215/225_P0/346/224/266/345/217/243_v0.1.md +270 -270
- 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
- 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
- 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
- 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
- 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
- package/docs/swarm//350/234/202/347/276/244/344/272/222/350/201/224_v0.1.md +704 -704
- 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
- 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
- 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
- package/docs/theory//346/231/272/350/203/275/350/256/2723.4.md +5260 -5260
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/dsh/README.md +82 -82
- package/dsh/cordis.yml.example +139 -139
- package/dsh/update-lingshu.bat +11 -11
- package/lib/hooks.js +36 -2
- package/lib/lib/roleplay_web.js +427 -427
- package/md_cg/__init__.py +7 -7
- package/md_cg/audit.py +368 -368
- package/md_cg/autonomy.py +287 -287
- package/md_cg/backfill.py +1327 -1327
- package/md_cg/backfill_bigdomain.py +34 -34
- package/md_cg/bench6_arms.py +410 -410
- package/md_cg/bench6_common.py +230 -230
- package/md_cg/bench6_competitors.py +212 -212
- package/md_cg/bench_axis_domain.py +257 -257
- package/md_cg/bench_blind_comp.py +308 -308
- package/md_cg/bench_en_atoms_public.py +230 -230
- package/md_cg/bench_governance.py +348 -348
- package/md_cg/bench_lme_zh.py +410 -410
- package/md_cg/bench_locomo.py +121 -121
- package/md_cg/bench_locomo_zh.py +450 -450
- package/md_cg/bench_locomo_zh_public.py +147 -147
- package/md_cg/bench_longmem.py +112 -112
- package/md_cg/bench_membench.py +632 -632
- package/md_cg/bench_p0.py +149 -149
- package/md_cg/bench_progressive.py +287 -287
- package/md_cg/bench_role_views.py +238 -238
- package/md_cg/bench_task_ab.py +243 -243
- package/md_cg/bench_task_ab_llm.py +408 -408
- package/md_cg/bench_unified_en.py +204 -204
- package/md_cg/bench_zh_mad.py +601 -601
- package/md_cg/blindspot_tickets.py +123 -123
- package/md_cg/branches.py +285 -285
- package/md_cg/build_postings.py +73 -73
- package/md_cg/ccgc.py +1005 -948
- package/md_cg/census.py +132 -132
- package/md_cg/chain.py +300 -300
- package/md_cg/codeindex.py +531 -531
- package/md_cg/coldverify.py +292 -292
- package/md_cg/comment_gate.py +337 -337
- package/md_cg/cond_compose.py +190 -190
- package/md_cg/cond_facts.py +154 -154
- package/md_cg/cond_template.json +106 -106
- package/md_cg/condition_anchor.py +142 -142
- package/md_cg/conformance.py +726 -726
- package/md_cg/consistency.py +717 -717
- package/md_cg/consolidate.py +1536 -1439
- package/md_cg/corpus.py +110 -110
- package/md_cg/crosscheck.py +1097 -1097
- package/md_cg/crypto.py +437 -437
- package/md_cg/d_meta.py +310 -310
- package/md_cg/datapath.py +334 -334
- package/md_cg/docindex.py +473 -473
- package/md_cg/eval_common.py +575 -575
- package/md_cg/evidence.py +580 -580
- package/md_cg/evolution.py +477 -477
- package/md_cg/export.py +220 -220
- package/md_cg/forgetting.py +581 -581
- package/md_cg/fsutil.py +329 -329
- package/md_cg/hotcache.py +238 -214
- package/md_cg/hyperedge.py +251 -251
- package/md_cg/identity.py +390 -390
- package/md_cg/insight.py +500 -500
- package/md_cg/interop.py +199 -0
- package/md_cg/lexicon/build_cedict_en_zh.py +329 -329
- package/md_cg/lexicon/build_standard_en.py +171 -171
- package/md_cg/lexicon/expand_en_zh.py +211 -211
- package/md_cg/lifecycle.py +272 -272
- package/md_cg/linkref.py +280 -280
- package/md_cg/links.py +622 -622
- package/md_cg/mcp_server.py +129 -32
- package/md_cg/md_whitebox.py +345 -345
- package/md_cg/mdcg.py +176 -117
- package/md_cg/mdcos.py +79 -13
- package/md_cg/metacognition.py +591 -591
- package/md_cg/migrate.py +119 -119
- package/md_cg/migrate_aeis.py +221 -221
- package/md_cg/migrate_roleplay.py +293 -293
- package/md_cg/migrate_wisdom_graph.py +360 -360
- package/md_cg/mreview/__init__.py +25 -25
- package/md_cg/mreview/__main__.py +110 -110
- package/md_cg/mreview/bundle.py +178 -178
- package/md_cg/mreview/candidates.py +262 -262
- package/md_cg/mreview/govern.py +693 -693
- package/md_cg/mreview/locate.py +939 -939
- package/md_cg/mreview/pipeline.py +728 -728
- package/md_cg/mreview/rules/duplication.json +21 -21
- package/md_cg/mreview/rules/field_coverage.json +54 -54
- package/md_cg/mreview/rules/source_license.json +21 -21
- package/md_cg/mreview/rules/template_flow.json +21 -21
- package/md_cg/mreview/ruleset.py +252 -252
- package/md_cg/nodefile.py +575 -575
- package/md_cg/pooling.py +484 -472
- package/md_cg/postings.py +298 -298
- package/md_cg/predict.py +1100 -1100
- package/md_cg/progressive.py +123 -123
- package/md_cg/protect.py +272 -272
- package/md_cg/protocol/md_cg_gate.proto +33 -33
- package/md_cg/protocol.py +372 -372
- package/md_cg/provenance.py +582 -582
- package/md_cg/reach.py +453 -453
- package/md_cg/readcache.py +85 -0
- package/md_cg/refindex.py +833 -833
- package/md_cg/refine.py +604 -604
- package/md_cg/roleviews.py +89 -89
- package/md_cg/routing.py +365 -365
- package/md_cg/scrub.py +852 -852
- package/md_cg/security.py +274 -274
- package/md_cg/self_state.py +1029 -1029
- package/md_cg/selfreport.py +151 -151
- package/md_cg/semantic/__init__.py +10 -10
- package/md_cg/semantic/canonical.py +122 -122
- package/md_cg/semantic/en_normalizer.py +364 -364
- package/md_cg/semantic/en_zh_map.json +28694 -0
- package/md_cg/semantic/export_en_zh_map.py +64 -0
- package/md_cg/semantic/unify.py +45 -0
- package/md_cg/semantic/zh_en_atoms.py +139 -139
- package/md_cg/signer.py +562 -562
- package/md_cg/sources.py +815 -582
- package/md_cg/statushdr.py +179 -179
- package/md_cg/stg.py +48 -37
- package/md_cg/subgraph.py +729 -729
- package/md_cg/sustain.py +1138 -1138
- package/md_cg/tasks.py +470 -470
- package/md_cg/test_action_derive.py +203 -203
- package/md_cg/test_audit_rotate.py +270 -270
- package/md_cg/test_autonomy.py +143 -143
- package/md_cg/test_bench_governance.py +102 -102
- package/md_cg/test_blindspot_tickets.py +166 -166
- package/md_cg/test_branches.py +249 -249
- package/md_cg/test_ccg_perturb.py +184 -184
- package/md_cg/test_ccgc.py +433 -433
- package/md_cg/test_census_prune.py +81 -81
- package/md_cg/test_cond_compose_anchors.py +76 -76
- package/md_cg/test_cond_match.py +165 -165
- package/md_cg/test_condition_anchor.py +81 -81
- package/md_cg/test_d_meta.py +412 -412
- package/md_cg/test_datapath_root.py +199 -199
- package/md_cg/test_en_pipeline.py +166 -166
- package/md_cg/test_gain_gate.py +212 -212
- package/md_cg/test_health_scale.py +173 -173
- package/md_cg/test_hive_ingest.py +285 -0
- package/md_cg/test_hot_cold.py +215 -215
- package/md_cg/test_hyperedge.py +245 -245
- package/md_cg/test_i26_empty_first_write.py +116 -0
- package/md_cg/test_i27_e041_identity.py +128 -0
- package/md_cg/test_i28_hotcache_prodpath.py +122 -0
- package/md_cg/test_identity_attribution.py +147 -147
- package/md_cg/test_index_durability.py +224 -224
- package/md_cg/test_interop.py +93 -0
- package/md_cg/test_lifecycle.py +309 -309
- package/md_cg/test_linkref.py +306 -306
- package/md_cg/test_lock.py +43 -43
- package/md_cg/test_md_access_parity.py +255 -255
- package/md_cg/test_md_writepath.py +345 -345
- package/md_cg/test_mdstore_search_parity.py +160 -0
- package/md_cg/test_mr_m2.py +587 -587
- package/md_cg/test_mr_m3.py +710 -710
- package/md_cg/test_mr_m4.py +485 -485
- package/md_cg/test_p0.py +250 -250
- package/md_cg/test_p1.py +316 -316
- package/md_cg/test_p10_identity.py +173 -173
- package/md_cg/test_p11_consistency.py +233 -233
- package/md_cg/test_p12_metacognition.py +212 -212
- package/md_cg/test_p13_encryption.py +241 -241
- package/md_cg/test_p14_sustain.py +249 -249
- package/md_cg/test_p15_scrub.py +280 -280
- package/md_cg/test_p16_self_state.py +301 -301
- package/md_cg/test_p17_predict.py +354 -354
- package/md_cg/test_p18_whitebox.py +171 -171
- package/md_cg/test_p19_migrate_roleplay.py +149 -149
- package/md_cg/test_p20_evolution.py +315 -315
- package/md_cg/test_p21_tokens.py +293 -270
- package/md_cg/test_p22_theory.py +175 -175
- package/md_cg/test_p23_links.py +311 -311
- package/md_cg/test_p24_evidence.py +227 -227
- package/md_cg/test_p25_weights.py +156 -156
- package/md_cg/test_p26_refindex.py +416 -416
- package/md_cg/test_p27_docindex.py +765 -765
- package/md_cg/test_p28_refcheck.py +305 -305
- package/md_cg/test_p29_session_ingest_export.py +354 -333
- package/md_cg/test_p3.py +11 -2
- package/md_cg/test_p30_maintain.py +330 -330
- package/md_cg/test_p31_insight.py +534 -534
- package/md_cg/test_p32_backfill.py +298 -298
- package/md_cg/test_p33_ccg_wiring.py +293 -293
- package/md_cg/test_p34_crosscheck.py +331 -331
- package/md_cg/test_p35_conditioned_claim.py +252 -252
- package/md_cg/test_p36_kp_align.py +230 -230
- package/md_cg/test_p37_condition_space.py +248 -248
- package/md_cg/test_p38_concurrent_flush.py +102 -0
- package/md_cg/test_p38_contextualize.py +273 -273
- package/md_cg/test_p39_verify_flow.py +113 -0
- package/md_cg/test_p39_vision_evidence.py +369 -369
- package/md_cg/test_p40_refine_worklist.py +241 -241
- package/md_cg/test_p41_evolve_patrol.py +224 -224
- package/md_cg/test_p42_provenance.py +269 -269
- package/md_cg/test_p43_pooling.py +412 -398
- package/md_cg/test_p44_md_whitebox.py +231 -231
- package/md_cg/test_p45_session_identity.py +219 -219
- package/md_cg/test_p46_unit_scope.py +272 -272
- package/md_cg/test_p47_session_view.py +281 -0
- package/md_cg/test_p4_fuzzy.py +223 -223
- package/md_cg/test_p5_semantic.py +226 -226
- package/md_cg/test_p6_consolidate.py +440 -387
- package/md_cg/test_p7_goals_recent.py +202 -202
- package/md_cg/test_p8_subgraph_chain.py +200 -200
- package/md_cg/test_p9_forget_protect.py +231 -231
- package/md_cg/test_predict_beta.py +135 -135
- package/md_cg/test_preflight_failclosed.py +100 -100
- package/md_cg/test_progressive.py +146 -146
- package/md_cg/test_protocol.py +243 -243
- package/md_cg/test_reach.py +378 -378
- package/md_cg/test_reach_keys.py +201 -201
- package/md_cg/test_read_clip.py +141 -141
- package/md_cg/test_readcache_prodpath.py +155 -0
- package/md_cg/test_retr_gates_prodpath.py +140 -0
- package/md_cg/test_retr_s1.py +340 -340
- package/md_cg/test_retr_s1b.py +209 -209
- package/md_cg/test_retr_s3.py +194 -194
- package/md_cg/test_retr_s4.py +163 -163
- package/md_cg/test_retr_s5.py +200 -200
- package/md_cg/test_retr_s6.py +157 -157
- package/md_cg/test_retr_s7.py +384 -384
- package/md_cg/test_retr_s8_time.py +369 -316
- package/md_cg/test_retr_s9_edges.py +286 -286
- package/md_cg/test_retr_s9_entity_ctx.py +175 -175
- package/md_cg/test_review_conformance.py +367 -367
- package/md_cg/test_role_views.py +354 -354
- package/md_cg/test_sem_noise.py +242 -242
- package/md_cg/test_semantic_canonical.py +241 -241
- package/md_cg/test_subproc_encoding.py +192 -192
- package/md_cg/test_sustain_mutual.py +153 -153
- package/md_cg/test_tasks.py +409 -409
- package/md_cg/test_tool_face.py +189 -189
- package/md_cg/test_transfer.py +180 -180
- package/md_cg/test_trust.py +361 -361
- package/md_cg/test_twophase.py +286 -286
- package/md_cg/test_v14_fixes.py +397 -397
- package/md_cg/test_validity_filter.py +280 -280
- package/md_cg/test_verify_answer.py +138 -138
- package/md_cg/test_wisdom_md_store.py +292 -292
- package/md_cg/test_writelimit.py +197 -197
- package/md_cg/test_writepipe.py +214 -214
- package/md_cg/theory.py +273 -273
- package/md_cg/tokens.py +677 -663
- package/md_cg/tool_face.py +260 -260
- package/md_cg/trust.py +986 -950
- package/md_cg/twophase.py +231 -231
- package/md_cg/units.py +667 -667
- package/md_cg/vision_evidence.py +666 -666
- package/md_cg/weights.py +624 -624
- package/md_cg/whitebox.py +527 -527
- package/md_cg/whitebox_kb/__init__.py +37 -37
- package/md_cg/whitebox_kb/aeis_core/__init__.py +42 -42
- package/md_cg/whitebox_kb/aeis_core/semantic.py +280 -280
- package/md_cg/whitebox_kb/aeis_core/textutil.py +13 -13
- package/md_cg/whitebox_kb/engine.py +310 -310
- package/md_cg/whitebox_kb/seed_knowledge//346/231/272/350/203/275/350/256/2723.4.md +5260 -5260
- package/md_cg/whitebox_kb/wisdom/browser_units.py +2631 -2631
- package/md_cg/whitebox_kb/wisdom/causal_discover.py +432 -432
- package/md_cg/whitebox_kb/wisdom/chat_engine.py +1506 -1506
- package/md_cg/whitebox_kb/wisdom/code_solidified.json +6195 -6195
- package/md_cg/whitebox_kb/wisdom/compiler_code_units.py +3033 -3033
- package/md_cg/whitebox_kb/wisdom/condition_algebra.py +112 -112
- package/md_cg/whitebox_kb/wisdom/condition_frame.py +315 -315
- package/md_cg/whitebox_kb/wisdom/condition_kb.py +108 -108
- package/md_cg/whitebox_kb/wisdom/conflict_map.json +4445 -4445
- package/md_cg/whitebox_kb/wisdom/core/lexer.py +512 -512
- package/md_cg/whitebox_kb/wisdom/core/name_checker.py +1023 -1023
- package/md_cg/whitebox_kb/wisdom/cspmn.py +258 -258
- package/md_cg/whitebox_kb/wisdom/csre.py +264 -264
- package/md_cg/whitebox_kb/wisdom/danmaku_audit.py +252 -252
- package/md_cg/whitebox_kb/wisdom/distilled_condition_units.json +6417 -6417
- package/md_cg/whitebox_kb/wisdom/docs/WB-EVAL-20260902b.json +1950 -1950
- package/md_cg/whitebox_kb/wisdom/docs/WB-EVAL-20260902c.json +1296 -1296
- package/md_cg/whitebox_kb/wisdom/docs/whitebox_capability_graph_demo.json +59 -59
- package/md_cg/whitebox_kb/wisdom/graph_db_units.py +3089 -3089
- package/md_cg/whitebox_kb/wisdom/knowledge_points.py +318 -318
- package/md_cg/whitebox_kb/wisdom/md_access.py +470 -470
- package/md_cg/whitebox_kb/wisdom/md_store.py +276 -251
- package/md_cg/whitebox_kb/wisdom/migrate_wisdom.py +476 -476
- package/md_cg/whitebox_kb/wisdom/navigate.py +248 -248
- package/md_cg/whitebox_kb/wisdom/neural_retrieve.py +224 -224
- package/md_cg/whitebox_kb/wisdom/os_units.py +2735 -2735
- package/md_cg/whitebox_kb/wisdom/pattern_separation.py +376 -376
- package/md_cg/whitebox_kb/wisdom/prereq_map.json +364 -364
- package/md_cg/whitebox_kb/wisdom/python_code_units.py +2766 -2766
- package/md_cg/whitebox_kb/wisdom/role_solidified.json +7 -7
- package/md_cg/whitebox_kb/wisdom/route_memory.py +244 -244
- package/md_cg/whitebox_kb/wisdom/scene_reconstruction.py +148 -148
- package/md_cg/whitebox_kb/wisdom/snr_report.json +40 -40
- package/md_cg/whitebox_kb/wisdom/test_code_compose_domains.py +7541 -7541
- package/md_cg/whitebox_kb/wisdom/test_compiler_self_bootstrap.py +354 -354
- package/md_cg/whitebox_kb/wisdom/test_ecosystem_assembly.py +158 -158
- package/md_cg/whitebox_kb/wisdom/test_ecosystem_demos.py +193 -193
- package/md_cg/whitebox_kb/wisdom/test_graph_db.py +292 -292
- package/md_cg/whitebox_kb/wisdom/test_python_self_bootstrap.py +160 -160
- package/md_cg/whitebox_kb/wisdom/trigger_words_index.json +4366 -4366
- package/md_cg/whitebox_kb/wisdom/verifier.py +1297 -1297
- package/md_cg/writelimit.py +356 -356
- package/md_cg/writepipe.py +550 -542
- package/package.json +97 -96
- package/skills/plugin.json +54 -54
- package/skills/skills/designer-perspective/SKILL.md +158 -158
- package/skills/skills/designer-perspective/references/01-observation-position.md +66 -66
- package/skills/skills/designer-perspective/references/02-structure-recognition.md +62 -62
- package/skills/skills/designer-perspective/references/03-direction-judgment.md +55 -55
- package/skills/skills/designer-perspective/references/04-qualification-verdict.md +72 -72
- package/skills/skills/designer-perspective/references/05-condition-attribution.md +74 -74
- package/skills/skills/designer-perspective/scripts/designer.py +545 -545
- package/skills/skills/designer-perspective/tests/cases.jsonl +17 -17
- package/skills/skills/designer-perspective/tests/selftest.py +61 -61
- package/skills/skills/lingshu-browser/SKILL.md +60 -60
- package/skills/skills/lingshu-compiler/SKILL.md +56 -56
- package/skills/skills/lingshu-compiler/units/analyze-type-infer/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/check-name-real/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-assign/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-expr-tree/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-full-pipeline/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-func-def/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-if-then/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-logic-expr/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-recursive/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-scope/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-type-check/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-while/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0010c4bf/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0355bffb/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0361708a/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-054a0414/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-05a1691a/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-05eeed1e/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0622a1f6/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-08b54217/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0a62b70c/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0ab24d00/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0e093688/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0e82b966/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0ee9b9b9/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0f3787e6/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-1028685f/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-11897630/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-16661b9b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-1f722303/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-25be1262/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-2a76ba07/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-2dbea54a/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-2df52f16/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-2ec2c9d2/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-2f8c8f39/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-38378ac7/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-39457d2e/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-39fb5926/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-47f4fbfa/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-4a8cd1f1/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-4b230b6b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-4cbbda95/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-4d5a68ff/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-56dc9bdd/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-57e76ebe/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-61ff016b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-63eae588/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-64c4224b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-66377955/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-674ab4f6/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-6af76fd6/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-6d979db2/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-6de326c0/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-6f1c0eea/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-724d6c9b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-7690177f/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-7b229a7d/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-7f471b3a/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-815cad08/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-83c61634/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-8c798c81/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-8dd747ac/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-90324d0a/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-94b8d72d/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-94f12231/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-95937c16/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-98a5625b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-98b4c42f/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-98e3894a/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-9bdfe4b8/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-9d9b4e83/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-9f7be5ad/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-a6daf076/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-a7995a0c/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-a8399248/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-aabbd099/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-afe169d8/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-b0678bda/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-b09bd196/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-b1396e23/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-b9b31ce0/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-c10264a7/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-cb1e8e4b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-ccafd438/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-cda9c262/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-ce648068/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-cf5776a4/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-d974e5d3/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-e3979fd3/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-eb1cf2b5/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-ecb30d5b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-f8c8b24b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-f99fedbe/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-fa8ff5f7/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-fe1b058d/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/lex-chinese-program/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/lex-dao-de-jing/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/lex-nine-chapters/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-arithmetic/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-array-ops/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-closure-call/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-closure-create/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-compare/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-cond-jump/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-cond-space/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-exception/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-func-call/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-loop-run/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-profiling/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-refcount/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-run-loop/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-short-circuit/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-stack-guard/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-stack-ops/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-trust-accum/SKILL.md +45 -45
- package/skills/skills/lingshu-graph/SKILL.md +63 -63
- package/skills/skills/lingshu-net/SKILL.md +48 -48
- package/skills/skills/lingshu-os/SKILL.md +64 -64
- package/skills/skills/lingshu-pylang/SKILL.md +71 -71
- package/src/bridge.ts +401 -401
- package/src/hooks.ts +38 -2
- package/src/lib/datapath.ts +326 -326
- package/src/lib/mdcg_client.ts +413 -413
- package/src/lib/mutual.ts +428 -428
- package/src/lib/prompt_safety.ts +62 -62
- package/src/lib/python_path.ts +71 -71
- package/src/lib/roleplay_web.ts +932 -932
- package/src/lib/token_store.ts +192 -192
- package/src/tools.ts +212 -212
- package/zcode/AGENTS.md +11 -3
- package/zcode/README.md +41 -41
- /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
package/md_cg/vision_evidence.py
CHANGED
|
@@ -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())
|