@furongjun1999/dsh-memory 0.4.11 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +552 -465
- 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 +143 -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/bench_lingshu_self/bench_self.py +140 -0
- package/docs/eval/bench_lingshu_self/self_bench_result.json +404 -0
- package/docs/eval/bench_lingshu_self//347/201/265/346/236/242/350/207/252/345/272/223/347/253/257/345/210/260/347/253/257/346/243/200/347/264/242/345/256/236/346/265/213_v1.0.md +38 -0
- package/docs/eval//344/270/215/345/217/257/351/235/240/346/200/247/350/220/275/345/234/260_P0_v1.0.md +185 -0
- 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/225/205/351/232/234/346/263/250/345/205/245/345/256/236/346/265/213_v1.0.md +422 -0
- 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/eval//347/253/257/345/210/260/347/253/257LoCoMoQA/345/220/214/345/217/243/345/276/204/345/257/271/347/205/247_v1.0.md +100 -0
- package/docs/eval//347/253/257/345/210/260/347/253/257/345/271/262/346/211/260/346/261/240/350/257/204/346/265/213_/347/241/256/345/256/232/346/200/247/350/243/201/345/206/263vsLLM_judge_v1.1.md +197 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v1.md +156 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v10.md +210 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v11.md +227 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v12.md +203 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v13.md +233 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v14.md +191 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v15.md +213 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v16.md +214 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v17.md +199 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v2.md +156 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v3.md +152 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v4.md +128 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v5.md +114 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v6.md +192 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v7.md +187 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v8.md +207 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v9.md +203 -0
- 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//345/256/211/345/205/250/345/256/241/350/256/241/345/256/236/351/224/232_v0.1.md +202 -0
- 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 +519 -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/images/lingshu-moonlight-covenant-poster-preview.jpg +0 -0
- package/docs/images/lingshu-moonlight-covenant-poster.png +0 -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//344/270/215/345/217/257/351/235/240/345/256/232/347/220/206/344/270/216/345/244/261/346/225/210/344/274/230/345/205/210/346/241/206/346/236/266_v0.3.md +365 -0
- 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//344/270/215/345/217/257/351/235/240/346/200/247/347/220/206/350/256/272_v0.1.md +343 -0
- 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/bridge.d.ts +9 -0
- package/lib/bridge.js +35 -0
- package/lib/hooks.js +36 -2
- package/lib/index.js +7 -1
- package/lib/lib/roleplay_web.js +116 -29
- package/lib/lib/token_store.d.ts +7 -1
- package/lib/lib/token_store.js +12 -3
- package/md_cg/__init__.py +7 -7
- package/md_cg/audit.py +379 -368
- package/md_cg/autonomy.py +287 -287
- package/md_cg/backfill.py +1328 -1327
- package/md_cg/backfill_bigdomain.py +34 -34
- package/md_cg/backfill_bucket_zh.py +35 -0
- 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_e2e_judge.py +532 -0
- package/md_cg/bench_e2e_locomo_qa.py +368 -0
- package/md_cg/bench_e2e_qa.py +256 -0
- 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 +301 -285
- package/md_cg/build_postings.py +73 -73
- package/md_cg/ccgc.py +1006 -948
- package/md_cg/census.py +132 -132
- package/md_cg/chain.py +315 -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 +1537 -1439
- package/md_cg/corpus.py +110 -110
- package/md_cg/crosscheck.py +1098 -1097
- package/md_cg/crypto.py +3 -1
- package/md_cg/d_meta.py +310 -310
- package/md_cg/datapath.py +78 -18
- package/md_cg/docindex.py +473 -473
- package/md_cg/eval_common.py +575 -575
- package/md_cg/evidence.py +4 -2
- package/md_cg/evolution.py +477 -477
- package/md_cg/export.py +222 -220
- package/md_cg/forgetting.py +581 -581
- package/md_cg/fsutil.py +377 -329
- package/md_cg/hotcache.py +48 -7
- 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 +338 -0
- package/md_cg/judgment_manifest.py +177 -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 +140 -107
- package/md_cg/mcp_server.py +403 -55
- package/md_cg/md_whitebox.py +345 -345
- package/md_cg/mdcg.py +783 -233
- package/md_cg/mdcos.py +500 -70
- 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 +694 -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 +300 -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 +143 -0
- package/md_cg/reconcile.py +228 -0
- package/md_cg/refindex.py +833 -833
- package/md_cg/refine.py +604 -604
- package/md_cg/review_cli.py +170 -0
- package/md_cg/roleviews.py +89 -89
- package/md_cg/routing.py +393 -365
- package/md_cg/run_tests.py +211 -0
- package/md_cg/scrub.py +13 -3
- package/md_cg/security.py +128 -18
- package/md_cg/self_state.py +1029 -1029
- package/md_cg/selfreport.py +152 -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 +7 -4
- package/md_cg/sources.py +816 -582
- package/md_cg/statushdr.py +179 -179
- package/md_cg/stg.py +54 -37
- package/md_cg/subgraph.py +729 -729
- package/md_cg/sustain.py +35 -5
- package/md_cg/tasks.py +470 -470
- package/md_cg/test_access_hints.py +147 -0
- 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_branch_discard_tombstone.py +136 -0
- package/md_cg/test_branches.py +13 -3
- 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_chain_read_isolate.py +168 -0
- 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_device_name.py +203 -0
- package/md_cg/test_datapath_root.py +199 -199
- package/md_cg/test_emit_negtail_cache.py +156 -0
- package/md_cg/test_en_pipeline.py +22 -2
- package/md_cg/test_gain_gate.py +212 -212
- package/md_cg/test_govern_directread.py +421 -0
- 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_i32_hotcache_env_key.py +218 -0
- package/md_cg/test_identity_attribution.py +96 -15
- package/md_cg/test_index_durability.py +17 -3
- package/md_cg/test_interop.py +95 -0
- package/md_cg/test_interop_judgment.py +228 -0
- package/md_cg/test_issue39_utf8_stdio.py +273 -0
- package/md_cg/test_lifecycle.py +309 -309
- package/md_cg/test_linkref.py +306 -306
- package/md_cg/test_links_concurrent_write.py +188 -0
- 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_merge_upsert.py +168 -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_n123_derive_expiry_chain.py +205 -0
- package/md_cg/test_n130_verify_falsified_protect.py +185 -0
- package/md_cg/test_n131_merge_gate.py +205 -0
- 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_p1x_ref_root.py +160 -0
- 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 +16 -7
- package/md_cg/test_p28_refcheck.py +305 -305
- package/md_cg/test_p29_session_ingest_export.py +354 -333
- package/md_cg/test_p2_mcp.py +3 -0
- 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 +7 -1
- 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 +153 -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 +316 -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_propose_tail_index.py +157 -0
- 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_read_scope_b27.py +277 -0
- package/md_cg/test_readcache_default_on.py +168 -0
- package/md_cg/test_readcache_precise_inval.py +270 -0
- package/md_cg/test_readcache_prodpath.py +203 -0
- package/md_cg/test_reconcile_v0.py +294 -0
- package/md_cg/test_retr_gates_prodpath.py +140 -0
- package/md_cg/test_retr_s1.py +344 -340
- package/md_cg/test_retr_s1b.py +276 -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 +392 -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 +9 -3
- package/md_cg/test_retr_score_once.py +208 -0
- package/md_cg/test_review_conformance.py +367 -367
- package/md_cg/test_review_onepass.py +170 -0
- package/md_cg/test_role_views.py +354 -354
- package/md_cg/test_rrf_graph_seed_cache.py +154 -0
- package/md_cg/test_security_audit.py +155 -0
- package/md_cg/test_security_audit_b26.py +161 -0
- package/md_cg/test_security_audit_v21.py +250 -0
- package/md_cg/test_sem_noise.py +242 -242
- package/md_cg/test_semantic_canonical.py +16 -2
- package/md_cg/test_session_isolation.py +168 -0
- package/md_cg/test_snapshot_autoclose.py +187 -0
- package/md_cg/test_subproc_encoding.py +192 -192
- package/md_cg/test_sustain_mutual.py +153 -153
- package/md_cg/test_tail_watermark_race.py +208 -0
- package/md_cg/test_tasks.py +409 -409
- package/md_cg/test_tenant_env_override_warn.py +139 -0
- package/md_cg/test_tenant_registry_corrupt_warn.py +151 -0
- 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 +38 -20
- package/md_cg/test_validity_filter.py +280 -280
- package/md_cg/test_verify_answer.py +138 -138
- package/md_cg/test_verify_dirty_reconcile.py +157 -0
- 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 +6 -3
- package/md_cg/tokens.py +85 -14
- 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 +668 -667
- package/md_cg/vision_evidence.py +667 -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/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/multilang_ir.py +128 -124
- 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 +20 -8
- package/package.json +101 -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 +33 -0
- package/src/hooks.ts +38 -2
- package/src/index.ts +526 -518
- 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 +116 -29
- package/src/lib/token_store.ts +13 -3
- 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
|
@@ -1,1297 +1,1297 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""verifier.py · 白箱本地校验器(Zero-LLM Verifier)v1
|
|
3
|
-
|
|
4
|
-
目标:把「白箱写代码 → 外部校验(是否符合规范/能否执行)」全部本地化,
|
|
5
|
-
彻底抛开大模型依赖。重复校验走本地缓存(替代 99.9% 的 LLM 缓存命中)。
|
|
6
|
-
|
|
7
|
-
设计:docs/白箱本地校验器_ZeroLLM_Verifier.md
|
|
8
|
-
|
|
9
|
-
校验链(六层,全部确定性):
|
|
10
|
-
① 校验指纹 → 本地缓存(已校验过 → 直接返回)
|
|
11
|
-
② L1 语法:ast.parse + 结构规则
|
|
12
|
-
③ L2 样例:输入→期望断言运行(物理基底裁决)
|
|
13
|
-
④ L3 边界:额外边界用例
|
|
14
|
-
⑤ 规范符合性:condition_kb 语义对齐 + 结构规范(白箱规则)
|
|
15
|
-
⑥ 集成测试:依赖单元组装 + 回归
|
|
16
|
-
|
|
17
|
-
用法:
|
|
18
|
-
python verifier.py --verify "os_112|工作窃取|<code文件路径>" # 校验单个
|
|
19
|
-
python verifier.py --cache-stats # 缓存统计
|
|
20
|
-
python verifier.py --self-test # 自测
|
|
21
|
-
"""
|
|
22
|
-
from __future__ import annotations
|
|
23
|
-
|
|
24
|
-
import ast
|
|
25
|
-
import copy
|
|
26
|
-
import hashlib
|
|
27
|
-
import importlib
|
|
28
|
-
import json
|
|
29
|
-
import os
|
|
30
|
-
import subprocess
|
|
31
|
-
import sys
|
|
32
|
-
import tempfile
|
|
33
|
-
import time
|
|
34
|
-
import traceback
|
|
35
|
-
import types
|
|
36
|
-
from dataclasses import dataclass, field, asdict
|
|
37
|
-
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
38
|
-
|
|
39
|
-
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
40
|
-
sys.path.insert(0, HERE)
|
|
41
|
-
|
|
42
|
-
CACHE_FILE = os.path.join(HERE, "..", "data", "verify_cache.json")
|
|
43
|
-
SAVINGS_LOG = os.path.join(HERE, "..", "data", "verify_savings.jsonl")
|
|
44
|
-
DATA_DIR = os.path.join(HERE, "..", "data")
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# ============ 一、数据结构 ============
|
|
48
|
-
|
|
49
|
-
def _stable_serialize(obj: Any) -> Any:
|
|
50
|
-
"""任意类型确定性序列化(缓存键基础)。
|
|
51
|
-
|
|
52
|
-
cases 里可能出现 set/bytes/lambda 等 json 不可序列化类型(如集合推导的
|
|
53
|
-
{1,2,3} 与 lambda、帧解析的 bytes)——指纹必须对它们稳定:
|
|
54
|
-
set/frozenset → 元素 str 排序化(去重保稳定)
|
|
55
|
-
bytes/bytearray → hex
|
|
56
|
-
callable(lambda/函数)→ __code__ 确定性摘要(co_code+常量+名,跨进程稳定,
|
|
57
|
-
不含内存地址——lambda 的 repr 含地址会导致重启后指纹漂移、缓存失效)
|
|
58
|
-
其余 → repr 回退(域单元 cases 无自定义类实例,足够)。
|
|
59
|
-
"""
|
|
60
|
-
if obj is None or isinstance(obj, (bool, int, float, str)):
|
|
61
|
-
return obj
|
|
62
|
-
if isinstance(obj, (list, tuple)):
|
|
63
|
-
return [_stable_serialize(x) for x in obj]
|
|
64
|
-
if isinstance(obj, (set, frozenset)):
|
|
65
|
-
return {"__set__": sorted(str(_stable_serialize(x)) for x in obj)}
|
|
66
|
-
if isinstance(obj, dict):
|
|
67
|
-
return {"__dict__": {str(k): _stable_serialize(v)
|
|
68
|
-
for k, v in sorted(obj.items(), key=lambda kv: str(kv[0]))}}
|
|
69
|
-
if isinstance(obj, (bytes, bytearray)):
|
|
70
|
-
return {"__bytes__": bytes(obj).hex()}
|
|
71
|
-
if isinstance(obj, types.CodeType):
|
|
72
|
-
# 嵌套 code 对象(lambda 的 co_consts 里可能含 <listcomp>/嵌套 lambda):
|
|
73
|
-
# repr 含内存地址(<code object at 0x…>)跨进程漂移——必须确定性摘要
|
|
74
|
-
digest = hashlib.sha256(json.dumps(
|
|
75
|
-
[obj.co_code.hex(), list(obj.co_consts), list(obj.co_names)],
|
|
76
|
-
ensure_ascii=False, sort_keys=True, default=_stable_serialize,
|
|
77
|
-
).encode("utf-8")).hexdigest()[:16]
|
|
78
|
-
return {"__code__": digest}
|
|
79
|
-
if callable(obj):
|
|
80
|
-
co = getattr(obj, "__code__", None)
|
|
81
|
-
if co is not None:
|
|
82
|
-
digest = hashlib.sha256(json.dumps(
|
|
83
|
-
[co.co_code.hex(), list(co.co_consts), list(co.co_names)],
|
|
84
|
-
ensure_ascii=False, sort_keys=True, default=_stable_serialize,
|
|
85
|
-
).encode("utf-8")).hexdigest()[:16]
|
|
86
|
-
return {"__fn__": digest}
|
|
87
|
-
return {"__fn__": repr(obj)}
|
|
88
|
-
try:
|
|
89
|
-
return {"__obj__": repr(obj)}
|
|
90
|
-
except Exception:
|
|
91
|
-
return {"__obj__": type(obj).__name__}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def _assert_match(got: Any, exp: Any) -> bool:
|
|
95
|
-
"""结果断言:dict 期望做子集匹配(got 包含 exp 全部键值即可)。
|
|
96
|
-
|
|
97
|
-
白箱单元常返回完整状态字典(如 VM 执行循环返回
|
|
98
|
-
{'trust':…, 'symbols':…, 'cond':…, 'stack':…}),而样例期望只关心其中
|
|
99
|
-
关键字段(如 {'trust': 0.8})——完整相等会误判,子集匹配符合「状态断言」语义。
|
|
100
|
-
"""
|
|
101
|
-
if isinstance(exp, dict) and isinstance(got, dict):
|
|
102
|
-
return all(got.get(k) == v for k, v in exp.items())
|
|
103
|
-
return got == exp
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
@dataclass
|
|
107
|
-
class VerifyRequest:
|
|
108
|
-
"""校验请求:白箱生成的代码 + 单元定义。
|
|
109
|
-
|
|
110
|
-
cases/deps 做深拷贝(不可变快照)——被校验代码可能原地修改输入
|
|
111
|
-
(如排序 arr[j], arr[j+1] = ...),若引用外部共享 list 会污染原始数据,
|
|
112
|
-
导致相同请求产生不同指纹(缓存失效)。
|
|
113
|
-
"""
|
|
114
|
-
task: str # 任务描述(排序/循环编译/图遍历…)
|
|
115
|
-
code: str # 白箱生成的代码
|
|
116
|
-
unit_id: str = "" # 单元标识(域+序号,如 os_112)
|
|
117
|
-
lang: str = "python" # 语言
|
|
118
|
-
cases: List[Tuple[Any, Any]] = field(default_factory=list) # (输入, 期望)
|
|
119
|
-
deps: List[str] = field(default_factory=list) # 依赖单元
|
|
120
|
-
expected_structure: Dict[str, Any] = field(default_factory=dict) # 规范约束
|
|
121
|
-
|
|
122
|
-
def __post_init__(self) -> None:
|
|
123
|
-
"""深拷贝防污染 + 指纹预计算(外部原地修改会让指纹漂移·缓存错配——P0 纪律)。"""
|
|
124
|
-
# 深拷贝可变字段——防止被校验代码原地修改污染外部共享对象
|
|
125
|
-
self.cases = copy.deepcopy(self.cases)
|
|
126
|
-
self.deps = list(self.deps)
|
|
127
|
-
self.expected_structure = dict(self.expected_structure)
|
|
128
|
-
|
|
129
|
-
def fingerprint(self) -> str:
|
|
130
|
-
"""确定性指纹:相同请求 → 相同指纹(缓存键)。
|
|
131
|
-
|
|
132
|
-
先整体经 _stable_serialize 归一化再 dumps——json.dumps 的 default 钩子
|
|
133
|
-
只处理值、不处理 dict 键(tuple 键如事件委托的 {('btn','click'):...}
|
|
134
|
-
直接崩溃),故必须把整个结构(含键)先递归归一化。
|
|
135
|
-
"""
|
|
136
|
-
payload = json.dumps(_stable_serialize({
|
|
137
|
-
"task": self.task, "code": self.code, "unit_id": self.unit_id,
|
|
138
|
-
"lang": self.lang, "cases": self.cases, "deps": self.deps,
|
|
139
|
-
"structure": self.expected_structure,
|
|
140
|
-
}), ensure_ascii=False, sort_keys=True)
|
|
141
|
-
return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:32]
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
@dataclass
|
|
145
|
-
class VerifyResult:
|
|
146
|
-
"""校验结果。"""
|
|
147
|
-
ok: bool
|
|
148
|
-
checks: List[Dict[str, Any]] = field(default_factory=list) # [{level, ok, evidence}]
|
|
149
|
-
fingerprint: str = ""
|
|
150
|
-
cached: bool = False
|
|
151
|
-
reason: str = ""
|
|
152
|
-
verified_at: str = "" # 校验时间戳(ISO)
|
|
153
|
-
version: int = 0 # 校验器规则版本(VERIFIER_VERSION 快照)
|
|
154
|
-
|
|
155
|
-
def summary(self) -> str:
|
|
156
|
-
parts = [f"✅ 通过" if self.ok else f"❌ 失败"]
|
|
157
|
-
for c in self.checks:
|
|
158
|
-
mark = "✓" if c["ok"] else "✗"
|
|
159
|
-
parts.append(f"{mark} {c['level']}")
|
|
160
|
-
if self.reason:
|
|
161
|
-
parts.append(f"原因: {self.reason}")
|
|
162
|
-
if self.version:
|
|
163
|
-
parts.append(f"v{self.version}")
|
|
164
|
-
return " | ".join(parts)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
# ============ 二、本地缓存(替代 LLM 缓存命中) ============
|
|
168
|
-
|
|
169
|
-
# 校验器规则版本:L1/L2/L3/规范/集成规则升级时必须 +1——
|
|
170
|
-
# 旧版本缓存结果在规则已变的场景下不再可信,强制全部失效重验
|
|
171
|
-
# (等价于「协议升级 → 相关缓存刷新」,GLM 建议的 protocol_version 落地)。
|
|
172
|
-
# v6(2026-08-31):cases 形态修复(list→tuple)后指纹归一化同值,旧 False
|
|
173
|
-
# 条目对修复免疫;bump 版本作废全部旧缓存——长驻进程内存快照复活毒条目的
|
|
174
|
-
# 终局解(版本不符整体清空,任何进程加载即重建)。
|
|
175
|
-
VERIFIER_VERSION = 10 # v10:结构规范的字面匹配改大小写不敏感——该表是语言无关的
|
|
176
|
-
# 任务语义判据(要求去重结构出现),字面取自 Python 命名习惯
|
|
177
|
-
# (set/seen),JS 标准 API 写作 `new Set(arr)`,敏感匹配把正确
|
|
178
|
-
# 实现判为「未见对应结构」(实测 code-js/去重 假失败)。v9:
|
|
179
|
-
# Python 模板(含 {fn} 占位符)先展开占位符再走 Python
|
|
180
|
-
# 判据——逐字复用 _render_placeholders(模板不再降级为文本判据,
|
|
181
|
-
# R1/R2/R3/R4 全量生效)。v8:非 Python 源码改走文本判据(Rust/JS
|
|
182
|
-
# 不再静默跳过假通过)+ `_audit_all` 审计面纳入语言单元表;
|
|
183
|
-
# v7:R3/R4 由死代码转为软模式恒执行 + 判据校准(入口/窗口/顶层)
|
|
184
|
-
# + evidence 诚实化——判据变更必须 bump,否则旧判定被缓存复用
|
|
185
|
-
_CACHE_VERSION_KEY = "_verifier_version"
|
|
186
|
-
_STATS_KEY = "_stats"
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
def estimate_tokens(text: str) -> int:
|
|
190
|
-
"""确定性 token 估算(用于「减少的 LLM 输入」度量,非精确计费)。
|
|
191
|
-
|
|
192
|
-
中文混合内容:中文字符按 1.2 token/字(中文 1 字 ≈ 1-1.5 token),
|
|
193
|
-
其余(代码/英文/标点)按 0.3 token/字符(英文/代码 1 token ≈ 3-4 字符)。
|
|
194
|
-
"""
|
|
195
|
-
cjk = sum(1 for ch in text if '\u4e00' <= ch <= '\u9fff')
|
|
196
|
-
other = len(text) - cjk
|
|
197
|
-
return int(cjk * 1.2 + other * 0.3)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
def _table_payload_chars() -> int:
|
|
201
|
-
"""「整张表」内容量:六域单元库定义(pattern+cases)总字符数。
|
|
202
|
-
|
|
203
|
-
设计文档背景:原方案「每次把整张表塞进 LLM 上下文查一次」——
|
|
204
|
-
表 = 白箱单元库(规则/模式/样例),本地化后表不再输入 LLM。
|
|
205
|
-
"""
|
|
206
|
-
from compiler_code_units import COMPILER_UNITS
|
|
207
|
-
from python_code_units import PYTHON_UNITS
|
|
208
|
-
from graph_db_units import GRAPH_UNITS
|
|
209
|
-
from os_units import OS_UNITS
|
|
210
|
-
from browser_units import BROWSER_UNITS
|
|
211
|
-
from net_units import NET_UNITS
|
|
212
|
-
total = 0
|
|
213
|
-
for units in (COMPILER_UNITS, PYTHON_UNITS, GRAPH_UNITS,
|
|
214
|
-
OS_UNITS, BROWSER_UNITS, NET_UNITS):
|
|
215
|
-
for uid, u in units.items():
|
|
216
|
-
total += len(uid) + len(u.get("task", "")) + len(u.get("pattern", "")) \
|
|
217
|
-
+ len(str(u.get("cases", [])))
|
|
218
|
-
return total
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
class VerifyCache:
|
|
222
|
-
"""校验结果本地缓存。相同指纹 → 零计算返回。
|
|
223
|
-
|
|
224
|
-
hits/misses 为进程内命中计数;「减少的 LLM 输入」以 append-only JSONL
|
|
225
|
-
审计日志持久化(data/verify_savings.jsonl,每行一次校验的输入度量)——
|
|
226
|
-
跨进程累计、可审计、可重放(复现证据)。
|
|
227
|
-
"""
|
|
228
|
-
|
|
229
|
-
def __init__(self, path: str = CACHE_FILE, version: int = VERIFIER_VERSION,
|
|
230
|
-
savings_log: Optional[str] = SAVINGS_LOG):
|
|
231
|
-
"""组装:缓存句柄注入(默认新建;变异测试可传隔离缓存)。"""
|
|
232
|
-
self.path = path
|
|
233
|
-
self.version = version
|
|
234
|
-
self.savings_log = savings_log
|
|
235
|
-
self._data: Dict[str, Dict[str, Any]] = {}
|
|
236
|
-
self.hits = 0
|
|
237
|
-
self.misses = 0
|
|
238
|
-
self._table_chars: Optional[int] = None
|
|
239
|
-
self._load()
|
|
240
|
-
|
|
241
|
-
def _load(self) -> None:
|
|
242
|
-
"""从磁盘回放缓存快照(版本不符整体作废防脏读)。"""
|
|
243
|
-
try:
|
|
244
|
-
if os.path.exists(self.path):
|
|
245
|
-
with open(self.path, "r", encoding="utf-8") as f:
|
|
246
|
-
self._data = json.load(f)
|
|
247
|
-
except Exception:
|
|
248
|
-
self._data = {}
|
|
249
|
-
# 版本不符 → 规则已变,旧缓存结果不可信,清空重建
|
|
250
|
-
if self._data.get(_CACHE_VERSION_KEY) != self.version:
|
|
251
|
-
self._data = {_CACHE_VERSION_KEY: self.version}
|
|
252
|
-
self._flush()
|
|
253
|
-
|
|
254
|
-
def record_input(self, payload_chars: int, hit: bool) -> None:
|
|
255
|
-
"""记录一次经本地校验的输入度量(append-only 审计日志)。
|
|
256
|
-
|
|
257
|
-
每次校验(含缓存命中)都意味着:该内容(表 + 请求)不再输入 LLM →
|
|
258
|
-
省下的 token 计入日志。hit 标记是否为缓存命中(重复校验零计算)。
|
|
259
|
-
savings_log=None(隔离模式,如变异测试)时不写日志。
|
|
260
|
-
"""
|
|
261
|
-
if not self.savings_log:
|
|
262
|
-
return
|
|
263
|
-
table = self._table_chars
|
|
264
|
-
if table is None:
|
|
265
|
-
try:
|
|
266
|
-
table = _table_payload_chars()
|
|
267
|
-
except Exception:
|
|
268
|
-
table = 0
|
|
269
|
-
self._table_chars = table
|
|
270
|
-
text = payload_chars + table
|
|
271
|
-
half = text // 2
|
|
272
|
-
tokens = estimate_tokens("中" * half + "x" * (text - half))
|
|
273
|
-
try:
|
|
274
|
-
os.makedirs(DATA_DIR, exist_ok=True)
|
|
275
|
-
with open(self.savings_log, "a", encoding="utf-8") as f:
|
|
276
|
-
f.write(json.dumps({
|
|
277
|
-
"t": time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
278
|
-
"payload_chars": payload_chars, "table_chars": table,
|
|
279
|
-
"saved_chars": text, "saved_tokens": tokens,
|
|
280
|
-
"hit": bool(hit),
|
|
281
|
-
}, ensure_ascii=False) + "\n")
|
|
282
|
-
except Exception:
|
|
283
|
-
pass
|
|
284
|
-
|
|
285
|
-
def savings(self) -> Dict[str, Any]:
|
|
286
|
-
"""跨进程累计:从审计日志聚合「减少的 LLM 输入」。
|
|
287
|
-
|
|
288
|
-
读 self.savings_log(与写入同源)——此前误读模块常量 SAVINGS_LOG,
|
|
289
|
-
自定义 savings_log 路径时写入 A 聚合读 B,聚合恒 0(issue #4 审计发现)。
|
|
290
|
-
"""
|
|
291
|
-
total_chars = total_tokens = hits = reqs = 0
|
|
292
|
-
log = self.savings_log
|
|
293
|
-
try:
|
|
294
|
-
if log and os.path.exists(log):
|
|
295
|
-
for line in open(log, encoding="utf-8"):
|
|
296
|
-
line = line.strip()
|
|
297
|
-
if not line:
|
|
298
|
-
continue
|
|
299
|
-
d = json.loads(line)
|
|
300
|
-
total_chars += d.get("saved_chars", 0)
|
|
301
|
-
total_tokens += d.get("saved_tokens", 0)
|
|
302
|
-
reqs += 1
|
|
303
|
-
if d.get("hit"):
|
|
304
|
-
hits += 1
|
|
305
|
-
except Exception:
|
|
306
|
-
pass
|
|
307
|
-
return {"requests": reqs, "hits": hits,
|
|
308
|
-
"saved_chars": total_chars, "saved_tokens": total_tokens}
|
|
309
|
-
|
|
310
|
-
def get(self, fingerprint: str) -> Optional[VerifyResult]:
|
|
311
|
-
"""按指纹取缓存校验结果:命中计 hits 并标记 cached=True;未命中计 misses 返回 None。"""
|
|
312
|
-
entry = self._data.get(fingerprint)
|
|
313
|
-
if entry is None:
|
|
314
|
-
self.misses += 1
|
|
315
|
-
return None
|
|
316
|
-
self.hits += 1
|
|
317
|
-
r = VerifyResult(ok=entry["ok"], fingerprint=fingerprint, cached=True)
|
|
318
|
-
r.checks = entry.get("checks", [])
|
|
319
|
-
r.reason = entry.get("reason", "")
|
|
320
|
-
r.verified_at = entry.get("verified_at", "")
|
|
321
|
-
r.version = entry.get("version", 0)
|
|
322
|
-
return r
|
|
323
|
-
|
|
324
|
-
def put(self, result: VerifyResult) -> None:
|
|
325
|
-
"""写入指纹→结果映射并落盘持久化(附版本键防跨版本脏读)。"""
|
|
326
|
-
self._data[result.fingerprint] = {
|
|
327
|
-
"ok": result.ok,
|
|
328
|
-
"checks": result.checks,
|
|
329
|
-
"reason": result.reason,
|
|
330
|
-
"verified_at": result.verified_at,
|
|
331
|
-
"version": result.version,
|
|
332
|
-
}
|
|
333
|
-
self._data[_CACHE_VERSION_KEY] = self.version
|
|
334
|
-
self._flush()
|
|
335
|
-
|
|
336
|
-
def _flush(self) -> None:
|
|
337
|
-
"""内存态刷盘:读-合并-原子替换(多进程并发写友好)。
|
|
338
|
-
|
|
339
|
-
bootstrap 循环进程与交互进程共用同一缓存文件——先吸收磁盘上
|
|
340
|
-
他进程新增的条目再写,消除「整文件写覆盖」;临时文件+os.replace
|
|
341
|
-
原子替换,消除「读到半写状态」。残余窗口仅剩同指纹同时写的
|
|
342
|
-
几毫秒,缓存可重建(最坏=丢条目重跑 verify),工程可接受。
|
|
343
|
-
"""
|
|
344
|
-
try:
|
|
345
|
-
os.makedirs(DATA_DIR, exist_ok=True)
|
|
346
|
-
try:
|
|
347
|
-
if os.path.exists(self.path):
|
|
348
|
-
with open(self.path, "r", encoding="utf-8") as f:
|
|
349
|
-
disk = json.load(f)
|
|
350
|
-
# 版本一致性守卫:磁盘条目版本与本实例不同 → 全部忽略
|
|
351
|
-
# (版本 bump 即整体作废,不能被合并逻辑复活旧条目)
|
|
352
|
-
if isinstance(disk, dict) and disk.get(_CACHE_VERSION_KEY) == self.version:
|
|
353
|
-
for k, v in disk.items():
|
|
354
|
-
if k not in self._data:
|
|
355
|
-
self._data[k] = v
|
|
356
|
-
except Exception:
|
|
357
|
-
pass # 磁盘半写/损坏 → 以内存为准
|
|
358
|
-
tmp = self.path + ".tmp"
|
|
359
|
-
with open(tmp, "w", encoding="utf-8") as f:
|
|
360
|
-
json.dump(self._data, f, ensure_ascii=False, indent=1)
|
|
361
|
-
os.replace(tmp, self.path)
|
|
362
|
-
except Exception:
|
|
363
|
-
pass
|
|
364
|
-
|
|
365
|
-
def stats(self) -> Dict[str, Any]:
|
|
366
|
-
"""聚合统计:条目总数、通过数、命中率(hits/(hits+misses))。"""
|
|
367
|
-
entries = {k: v for k, v in self._data.items() if k != _CACHE_VERSION_KEY}
|
|
368
|
-
n = len(entries)
|
|
369
|
-
n_pass = sum(1 for v in entries.values() if v.get("ok"))
|
|
370
|
-
total_reqs = self.hits + self.misses
|
|
371
|
-
hit_rate = round(100.0 * self.hits / total_reqs, 1) if total_reqs else 0.0
|
|
372
|
-
return {"total": n, "passed": n_pass, "failed": n - n_pass,
|
|
373
|
-
"hits": self.hits, "misses": self.misses,
|
|
374
|
-
"hit_rate": hit_rate}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
# ============ 三、校验器 ============
|
|
378
|
-
|
|
379
|
-
#: 唯一真源(codeindex._body_comments)的解析缓存:None=未解析,False=不可用
|
|
380
|
-
_CI_BODY = None
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
def _ci_body_comments():
|
|
384
|
-
"""解析并缓存 `codeindex._body_comments`(body 窗口的唯一真源)。
|
|
385
|
-
|
|
386
|
-
三种加载形态各试一次;全部失败返回 None(调用方走等价兜底)。
|
|
387
|
-
缓存避免每次校验都付一次 import 代价(暖缓存路径要求零额外开销)。
|
|
388
|
-
"""
|
|
389
|
-
global _CI_BODY
|
|
390
|
-
if _CI_BODY is None:
|
|
391
|
-
fn = False
|
|
392
|
-
cands = ["md_cg.codeindex"]
|
|
393
|
-
pkg = __package__ or ""
|
|
394
|
-
if pkg.count(".") >= 2: # 如 md_cg.whitebox_kb.wisdom
|
|
395
|
-
cands.insert(0, pkg.rsplit(".", 1)[0].rsplit(".", 1)[0] + ".codeindex")
|
|
396
|
-
for mod in cands:
|
|
397
|
-
try:
|
|
398
|
-
fn = getattr(importlib.import_module(mod), "_body_comments", False)
|
|
399
|
-
except Exception: # noqa: BLE001
|
|
400
|
-
continue
|
|
401
|
-
if fn:
|
|
402
|
-
break
|
|
403
|
-
_CI_BODY = fn or False
|
|
404
|
-
return _CI_BODY or None
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
class Verifier:
|
|
408
|
-
"""本地校验器:六层校验链,零 LLM。"""
|
|
409
|
-
|
|
410
|
-
def __init__(self, cache: Optional[VerifyCache] = None):
|
|
411
|
-
"""组装:缓存句柄注入(默认新建;变异测试可传隔离缓存)。"""
|
|
412
|
-
self.cache = cache or VerifyCache()
|
|
413
|
-
|
|
414
|
-
# ---------- 主入口 ----------
|
|
415
|
-
def verify(self, req: VerifyRequest) -> VerifyResult:
|
|
416
|
-
"""六层校验入口:深拷贝请求→算指纹→命中直返;未命中逐层校验并记录 token 节省度量。
|
|
417
|
-
|
|
418
|
-
(深拷贝防调用方原地修改污染指纹空间——见 P0 修复记录。)"""
|
|
419
|
-
# 不可变快照:verify 内部运行样例可能原地修改输入(如排序
|
|
420
|
-
# arr[j], arr[j+1] = ...),必须深拷贝防止污染原始 req 对象
|
|
421
|
-
# (否则同一 req 第二次校验指纹变化,缓存失效)
|
|
422
|
-
req = copy.deepcopy(req)
|
|
423
|
-
fp = req.fingerprint()
|
|
424
|
-
|
|
425
|
-
# ① 缓存命中 → 直接返回
|
|
426
|
-
cached = self.cache.get(fp)
|
|
427
|
-
|
|
428
|
-
# 输入度量:经本地校验的请求内容量(本应输入 LLM 的「表 + 请求」——
|
|
429
|
-
# 无论命中与否,走本地 = 不再输入 LLM = 省下的 token;append 审计日志)
|
|
430
|
-
try:
|
|
431
|
-
self.cache.record_input(len(req.task) + len(req.code)
|
|
432
|
-
+ len(str(req.cases)) + len(req.unit_id),
|
|
433
|
-
hit=(cached is not None))
|
|
434
|
-
except Exception:
|
|
435
|
-
pass
|
|
436
|
-
|
|
437
|
-
if cached is not None:
|
|
438
|
-
return cached
|
|
439
|
-
|
|
440
|
-
checks: List[Dict[str, Any]] = []
|
|
441
|
-
|
|
442
|
-
# ② L1 语法
|
|
443
|
-
checks.append(self._check_l1_syntax(req))
|
|
444
|
-
|
|
445
|
-
# 注入型单元(expected_structure.inject,如编译-类型检查/图遍历-BFS):
|
|
446
|
-
# 函数依赖外部注入(Graph/infer_fn/组装单元),单独运行 L2/L3 无意义
|
|
447
|
-
# (与 code_compose verify_code 的 needs_inject 语义一致——由集成测试覆盖)
|
|
448
|
-
injected = req.expected_structure.get("inject")
|
|
449
|
-
|
|
450
|
-
# ③ L2 样例(语法通过才运行;注入型跳过)
|
|
451
|
-
if checks[-1]["ok"] and not injected:
|
|
452
|
-
checks.append(self._check_l2_samples(req))
|
|
453
|
-
|
|
454
|
-
# ④ L3 边界(注入型跳过)
|
|
455
|
-
if checks[-1]["ok"] and not injected:
|
|
456
|
-
checks.append(self._check_l3_boundary(req))
|
|
457
|
-
|
|
458
|
-
# ⑤ 规范符合性
|
|
459
|
-
checks.append(self._check_spec_compliance(req))
|
|
460
|
-
|
|
461
|
-
# ⑥ 集成测试(有 deps 才做)
|
|
462
|
-
if req.deps:
|
|
463
|
-
checks.append(self._check_integration(req))
|
|
464
|
-
|
|
465
|
-
ok = all(c["ok"] for c in checks)
|
|
466
|
-
reason = ""
|
|
467
|
-
if not ok:
|
|
468
|
-
failed = [c for c in checks if not c["ok"]]
|
|
469
|
-
reason = failed[0]["evidence"] if failed else "未知失败"
|
|
470
|
-
|
|
471
|
-
result = VerifyResult(ok=ok, checks=checks, fingerprint=fp, reason=reason,
|
|
472
|
-
verified_at=time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
473
|
-
version=VERIFIER_VERSION)
|
|
474
|
-
# 写缓存
|
|
475
|
-
self.cache.put(result)
|
|
476
|
-
return result
|
|
477
|
-
|
|
478
|
-
# ---------- L1 语法 ----------
|
|
479
|
-
def _check_l1_syntax(self, req: VerifyRequest) -> Dict[str, Any]:
|
|
480
|
-
"""ast.parse + 结构规则(函数定义/占位残留/括号平衡)。"""
|
|
481
|
-
code = req.code
|
|
482
|
-
# 语法解析
|
|
483
|
-
try:
|
|
484
|
-
tree = ast.parse(code)
|
|
485
|
-
except SyntaxError as e:
|
|
486
|
-
return {"level": "L1语法", "ok": False,
|
|
487
|
-
"evidence": f"语法错误: {e}"}
|
|
488
|
-
|
|
489
|
-
# 结构规则
|
|
490
|
-
errors = []
|
|
491
|
-
funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
|
|
492
|
-
if not funcs:
|
|
493
|
-
errors.append("无函数定义")
|
|
494
|
-
# 占位残留({fn} 等模板占位符未替换)
|
|
495
|
-
if "{fn}" in code or "{param" in code:
|
|
496
|
-
errors.append("存在未替换的模板占位符")
|
|
497
|
-
# 括号平衡:ast.parse 已通过即证明语法合法(字符串/注释内括号
|
|
498
|
-
# 不应计入——字符计数会误判,如 pattern 注释含裸括号)
|
|
499
|
-
# 禁止裸 import(白箱代码应自包含)——但允许标准库
|
|
500
|
-
imports = [n for n in ast.walk(tree) if isinstance(n, (ast.Import, ast.ImportFrom))]
|
|
501
|
-
STDLIB_OK = {"collections", "typing", "functools", "itertools", "math",
|
|
502
|
-
"random", "json", "re", "string", "dataclasses", "abc",
|
|
503
|
-
"os", "sys", "io", "struct", "asyncio", "heapq", "queue",
|
|
504
|
-
"threading", "time", "socket", "hashlib", "uuid", "base64",
|
|
505
|
-
"statistics", "bisect", "decimal", "fractions"}
|
|
506
|
-
if imports and not req.expected_structure.get("allow_import"):
|
|
507
|
-
bad = []
|
|
508
|
-
for n in imports:
|
|
509
|
-
if isinstance(n, ast.ImportFrom):
|
|
510
|
-
top = (n.module or "").split(".")[0]
|
|
511
|
-
if top and top not in STDLIB_OK:
|
|
512
|
-
bad.append(n.module)
|
|
513
|
-
else:
|
|
514
|
-
for alias in n.names:
|
|
515
|
-
top = alias.name.split(".")[0]
|
|
516
|
-
if top not in STDLIB_OK:
|
|
517
|
-
bad.append(alias.name)
|
|
518
|
-
if bad:
|
|
519
|
-
return {"level": "L1语法", "ok": False,
|
|
520
|
-
"evidence": f"存在非标准库导入: {bad}"}
|
|
521
|
-
|
|
522
|
-
if errors:
|
|
523
|
-
return {"level": "L1语法", "ok": False, "evidence": "; ".join(errors)}
|
|
524
|
-
return {"level": "L1语法", "ok": True,
|
|
525
|
-
"evidence": f"语法通过({len(funcs)} 个函数定义)"}
|
|
526
|
-
|
|
527
|
-
# ---------- L2 样例 ----------
|
|
528
|
-
def _check_l2_samples(self, req: VerifyRequest) -> Dict[str, Any]:
|
|
529
|
-
"""每个 (输入, 期望) 运行断言。"""
|
|
530
|
-
if not req.cases:
|
|
531
|
-
return {"level": "L2样例", "ok": True, "evidence": "无样例(跳过)"}
|
|
532
|
-
|
|
533
|
-
ns: Dict[str, Any] = {}
|
|
534
|
-
try:
|
|
535
|
-
exec(compile(req.code, "<verify>", "exec"), ns)
|
|
536
|
-
except Exception as e:
|
|
537
|
-
return {"level": "L2样例", "ok": False,
|
|
538
|
-
"evidence": f"代码编译/执行失败: {e}"}
|
|
539
|
-
|
|
540
|
-
# 找被测函数(第一个函数定义)
|
|
541
|
-
func_name = None
|
|
542
|
-
try:
|
|
543
|
-
tree = ast.parse(req.code)
|
|
544
|
-
funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
|
|
545
|
-
func_name = funcs[0].name if funcs else None
|
|
546
|
-
except Exception:
|
|
547
|
-
pass
|
|
548
|
-
|
|
549
|
-
if not func_name or func_name not in ns:
|
|
550
|
-
return {"level": "L2样例", "ok": False,
|
|
551
|
-
"evidence": f"找不到被测函数(期望 {func_name})"}
|
|
552
|
-
|
|
553
|
-
fn = ns[func_name]
|
|
554
|
-
ran = 0
|
|
555
|
-
for case_idx, (inp, exp) in enumerate(req.cases, 1):
|
|
556
|
-
if inp == "call":
|
|
557
|
-
# 'call' 特殊标记:域注入型单元(L2 由域集成测试覆盖,
|
|
558
|
-
# 与 code_compose verify_code 语义一致——不把 'call' 当输入)
|
|
559
|
-
continue
|
|
560
|
-
ran += 1
|
|
561
|
-
try:
|
|
562
|
-
got = fn(inp) if not isinstance(inp, tuple) else fn(*inp)
|
|
563
|
-
except Exception as e:
|
|
564
|
-
return {"level": "L2样例", "ok": False,
|
|
565
|
-
"evidence": f"样例{case_idx} 崩溃: {inp} → {e}"}
|
|
566
|
-
if not _assert_match(got, exp):
|
|
567
|
-
return {"level": "L2样例", "ok": False,
|
|
568
|
-
"evidence": f"样例{case_idx} 失败: {inp} → {got}(期望 {exp})"}
|
|
569
|
-
if ran == 0:
|
|
570
|
-
return {"level": "L2样例", "ok": True,
|
|
571
|
-
"evidence": "注入型单元(样例均为 call 标记,由集成测试覆盖)"}
|
|
572
|
-
return {"level": "L2样例", "ok": True,
|
|
573
|
-
"evidence": f"{ran} 组样例全部通过"}
|
|
574
|
-
|
|
575
|
-
# ---------- L3 边界 ----------
|
|
576
|
-
def _check_l3_boundary(self, req: VerifyRequest) -> Dict[str, Any]:
|
|
577
|
-
"""额外边界用例:None/空/极端值(仅单参数函数——多参数函数
|
|
578
|
-
边界形态依赖参数语义,统一生成会误判,由单元自带样例覆盖)。"""
|
|
579
|
-
extra_cases = self._gen_boundary_cases(req.task)
|
|
580
|
-
if not extra_cases:
|
|
581
|
-
return {"level": "L3边界", "ok": True, "evidence": "无额外边界用例"}
|
|
582
|
-
|
|
583
|
-
ns: Dict[str, Any] = {}
|
|
584
|
-
try:
|
|
585
|
-
exec(compile(req.code, "<verify>", "exec"), ns)
|
|
586
|
-
except Exception as e:
|
|
587
|
-
return {"level": "L3边界", "ok": False, "evidence": f"执行失败: {e}"}
|
|
588
|
-
|
|
589
|
-
func_name = None
|
|
590
|
-
n_args = None
|
|
591
|
-
try:
|
|
592
|
-
tree = ast.parse(req.code)
|
|
593
|
-
funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
|
|
594
|
-
if funcs:
|
|
595
|
-
func_name = funcs[0].name
|
|
596
|
-
args = funcs[0].args
|
|
597
|
-
n_args = len(args.posonlyargs) + len(args.args)
|
|
598
|
-
except Exception:
|
|
599
|
-
pass
|
|
600
|
-
|
|
601
|
-
if not func_name or func_name not in ns:
|
|
602
|
-
return {"level": "L3边界", "ok": True, "evidence": "跳过(无函数)"}
|
|
603
|
-
|
|
604
|
-
if n_args is not None and n_args != 1:
|
|
605
|
-
return {"level": "L3边界", "ok": True,
|
|
606
|
-
"evidence": f"跳过(函数 {n_args} 个参数,边界形态依赖参数语义)"}
|
|
607
|
-
|
|
608
|
-
# 单参数但参数是图/状态/字典类(adj/graph/table/state…)→ 边界形态
|
|
609
|
-
# 依赖具体语义(如 [] 传 max_clique(adj) 是类型错误而非代码缺陷)——
|
|
610
|
-
# 由单元自带样例覆盖,不统一生成边界
|
|
611
|
-
DICTISH_ARGS = {"adj", "graph", "g", "table", "state", "env", "cond",
|
|
612
|
-
"nodes", "node", "map", "units", "db", "queue",
|
|
613
|
-
"buffer", "registry", "cert"}
|
|
614
|
-
if n_args == 1 and funcs[0].args.args \
|
|
615
|
-
and funcs[0].args.args[0].arg in DICTISH_ARGS:
|
|
616
|
-
return {"level": "L3边界", "ok": True,
|
|
617
|
-
"evidence": f"跳过(参数 {funcs[0].args.args[0].arg} 为图/状态类,边界由样例覆盖)"}
|
|
618
|
-
|
|
619
|
-
fn = ns[func_name]
|
|
620
|
-
for case_idx, (inp, exp) in enumerate(extra_cases, 1):
|
|
621
|
-
try:
|
|
622
|
-
got = fn(inp) if not isinstance(inp, tuple) else fn(*inp)
|
|
623
|
-
except Exception as e:
|
|
624
|
-
# 边界用例允许异常返回(如空列表→None/报错均可接受)
|
|
625
|
-
if exp == "any":
|
|
626
|
-
continue
|
|
627
|
-
return {"level": "L3边界", "ok": False,
|
|
628
|
-
"evidence": f"边界用例{case_idx} 崩溃: {inp} → {e}"}
|
|
629
|
-
if exp != "any" and not _assert_match(got, exp):
|
|
630
|
-
return {"level": "L3边界", "ok": False,
|
|
631
|
-
"evidence": f"边界用例{case_idx} 失败: {inp} → {got}(期望 {exp})"}
|
|
632
|
-
return {"level": "L3边界", "ok": True,
|
|
633
|
-
"evidence": f"{len(extra_cases)} 组边界用例通过"}
|
|
634
|
-
|
|
635
|
-
def _gen_boundary_cases(self, task: str) -> List[Tuple[Any, Any]]:
|
|
636
|
-
"""根据任务类型生成边界用例(白箱规则,非 LLM)。
|
|
637
|
-
|
|
638
|
-
注意:不带「反转」——字典反转等单元收 dict(值变键),序列边界
|
|
639
|
-
[] 是类型错误而非代码缺陷;反转边界由单元自带样例覆盖。
|
|
640
|
-
"""
|
|
641
|
-
t = task.lower()
|
|
642
|
-
if any(w in t for w in ("排序", "sort", "去重", "dedup")):
|
|
643
|
-
return [([], []), ([1], [1]), (None, "any")]
|
|
644
|
-
if any(w in t for w in ("最大", "max", "最小", "min")):
|
|
645
|
-
return [([], None), ([5], 5), (None, "any")] # 空列表→None(与单元语义一致)
|
|
646
|
-
if any(w in t for w in ("求和", "sum")):
|
|
647
|
-
return [([], 0), (None, "any")]
|
|
648
|
-
if any(w in t for w in ("计数", "count", "频率", "freq")):
|
|
649
|
-
return [([], {}), (None, "any")] # Counter 语义:空列表 → {}
|
|
650
|
-
# 字符串族:拆分/替换/拼接/判断/对齐/哈希/分词/格式化——空串语义因函数而异
|
|
651
|
-
# (分词空串→[]、拼接空列表→''),统一断言会误判 → 只查「空串处理不崩溃」
|
|
652
|
-
if any(w in t for w in ("字符串", "拆分", "替换", "拼接", "判断", "对齐",
|
|
653
|
-
"哈希", "分词", "格式化")):
|
|
654
|
-
return [("", "any")]
|
|
655
|
-
# 数学族:舍入/统计/数学函数/均值/众数/分位数——零输入形态因函数而异
|
|
656
|
-
# (列表统计收 []、数值函数收 0),统一断言会误判 → 只查「零输入不崩溃」
|
|
657
|
-
if any(w in t for w in ("舍入", "统计", "数学函数", "均值", "众数", "分位数")):
|
|
658
|
-
return [(0, "any")]
|
|
659
|
-
return []
|
|
660
|
-
|
|
661
|
-
# ---------- ⑤ 规范符合性(白箱规则 + condition_kb) ----------
|
|
662
|
-
def _check_spec_compliance(self, req: VerifyRequest) -> Dict[str, Any]:
|
|
663
|
-
"""结构规范 + 语义对齐(condition_kb 查条件路由表)。"""
|
|
664
|
-
errors = []
|
|
665
|
-
|
|
666
|
-
# 结构规范:任务关键词 → 应包含的结构(白箱规则)
|
|
667
|
-
# 注意:只保留「强信号」结构(排序/去重/反转有字面可查的典型结构)。
|
|
668
|
-
# 「循环」类任务在编译器域以字节码/展开形式实现(无 for/while 字面量),
|
|
669
|
-
# 统一强查会误判——循环语义由 L2 样例裁决,不在此字面强查。
|
|
670
|
-
# 排序:接受比较符、sorted(key=) 键控、reorder 重排(排序键控/报文重排序)
|
|
671
|
-
# 反转:排除「字典反转」(值变键语义,无序列反转结构)
|
|
672
|
-
structure_rules = {
|
|
673
|
-
"排序": ["<", ">", "sorted", "key=", "reorder"],
|
|
674
|
-
"sort": ["<", ">", "sorted", "key=", "reorder"],
|
|
675
|
-
"去重": ["set", "seen"], "dedup": ["set", "seen"],
|
|
676
|
-
"反转": ["::-1", "reverse", "reversed"],
|
|
677
|
-
}
|
|
678
|
-
for kw, patterns in structure_rules.items():
|
|
679
|
-
if kw in req.task and req.expected_structure.get("skip_structure"):
|
|
680
|
-
continue
|
|
681
|
-
if kw in req.task:
|
|
682
|
-
if kw in ("反转",) and "字典" in req.task:
|
|
683
|
-
continue # 字典反转:值变键语义,无序列反转结构
|
|
684
|
-
# 大小写不敏感:本表是**语言无关的任务语义判据**(要求「去重结构
|
|
685
|
-
# 出现」这一事实),而字面取自 Python 命名习惯(set/seen)——
|
|
686
|
-
# JS 标准 API 写作 `new Set(arr)`,敏感匹配把正确实现判为
|
|
687
|
-
# 「未见对应结构」(实测 code-js/去重 假失败,v10 修正)。
|
|
688
|
-
# 放宽仅限大小写,令牌本身仍必须出现(不弱化判据强度)。
|
|
689
|
-
hit = any(p.lower() in req.code.lower() for p in patterns)
|
|
690
|
-
if not hit and not req.expected_structure.get("structure_optional"):
|
|
691
|
-
errors.append(f"任务含「{kw}」但代码未见对应结构 {patterns}")
|
|
692
|
-
|
|
693
|
-
# 语义对齐:condition_kb 查该任务的条件规律,代码应有对应处理
|
|
694
|
-
try:
|
|
695
|
-
from condition_kb import ConditionKB
|
|
696
|
-
kb = ConditionKB()
|
|
697
|
-
sem = kb.lookup(req.task)
|
|
698
|
-
if sem and sem.get("match"):
|
|
699
|
-
# 查到的规律描述包含条件词 → 代码应体现条件分支
|
|
700
|
-
cond_words = ["如果", "若", "when", "if", "条件", "情况"]
|
|
701
|
-
has_cond = any(w in req.code for w in cond_words)
|
|
702
|
-
if any(w in sem.get("match", "") for w in ["条件", "如果", "当"]) \
|
|
703
|
-
and not has_cond \
|
|
704
|
-
and not req.expected_structure.get("no_cond_needed"):
|
|
705
|
-
errors.append(f"condition_kb 提示「{sem.get('match', '')[:30]}」需条件处理但代码无分支")
|
|
706
|
-
except Exception:
|
|
707
|
-
pass # condition_kb 不可用时跳过(不影响主校验)
|
|
708
|
-
|
|
709
|
-
# 条件论注释规范(WB-SPEC v1.2,用户条件论要求):
|
|
710
|
-
# R1 函数/类等抽象的总体功能必须中文注释(docstring 或紧跟 # 中文注释)
|
|
711
|
-
# R2 注释须为中文语境(英文缩写嵌入中文说明;纯英文注释行违规)
|
|
712
|
-
# R3/R4(六要素契约)恒执行并回报达标率——见 _check_comment_spec 的纪律说明
|
|
713
|
-
self._comment_notes = []
|
|
714
|
-
self._ccg_stat = {}
|
|
715
|
-
errors.extend(self._check_comment_spec(req))
|
|
716
|
-
|
|
717
|
-
if errors:
|
|
718
|
-
return {"level": "规范符合性", "ok": False, "ccg": self._ccg_stat,
|
|
719
|
-
"evidence": "; ".join(errors)}
|
|
720
|
-
# evidence 必须**如实**:改造前无论 R3/R4 是否执行都写「条件论注释通过」,
|
|
721
|
-
# 等于把「没检查」冒充「检查通过」(固化记录里留下了这类伪证)。
|
|
722
|
-
detail = ";".join(self._comment_notes) or "R3/R4 无可检符号"
|
|
723
|
-
return {"level": "规范符合性", "ok": True, "ccg": self._ccg_stat,
|
|
724
|
-
"evidence": f"结构规范 + 语义对齐 + 条件论注释 R1/R2 通过({detail})"}
|
|
725
|
-
|
|
726
|
-
def _check_comment_spec(self, req: VerifyRequest) -> list:
|
|
727
|
-
"""条件论注释规范(WB-SPEC v1.2):R1 函数/类中文注释 + R2 注释中文语境。
|
|
728
|
-
|
|
729
|
-
R1:每个 FunctionDef/AsyncFunctionDef/ClassDef 必须有 docstring 或
|
|
730
|
-
紧跟的 # 中文注释描述其总体功能。
|
|
731
|
-
R2:注释行必须为中文语境(含中文字符)——英文缩写嵌入中文说明;
|
|
732
|
-
纯英文注释行(无任何中文)违规。
|
|
733
|
-
非 Python 源码分两档(见下方 except 分支):Python 模板(含 `{fn}` 占位符)
|
|
734
|
-
展开后走本函数的**全量**判据;真·非 Python(Rust/JS)走文本判据。
|
|
735
|
-
expected_structure.no_comment_spec=True 可豁免(如注入型组装片段)。
|
|
736
|
-
"""
|
|
737
|
-
if req.expected_structure.get("no_comment_spec"):
|
|
738
|
-
return []
|
|
739
|
-
code = req.code
|
|
740
|
-
try:
|
|
741
|
-
tree = ast.parse(code)
|
|
742
|
-
except SyntaxError:
|
|
743
|
-
# 分支 A·Python 模板:`*_UNITS` 的 pattern 是**带占位符的模板**
|
|
744
|
-
# (`def {fn}(arr):`),ast.parse 必然 SyntaxError。改造前这类模板
|
|
745
|
-
# 与 Rust/JS 一样落到 `return []` 静默跳过;v8 起落到文本判据——
|
|
746
|
-
# 但那是**降级判据**(只查三要素字面,不查 R1 逐符号中文注释 / R2
|
|
747
|
-
# 纯英文注释行)。故此优先展开占位符再进 Python 判据(v9),
|
|
748
|
-
# 使 Python 模板与六域单元获得同强度判据。
|
|
749
|
-
rendered = self._render_placeholders(code, req)
|
|
750
|
-
if rendered != code:
|
|
751
|
-
try:
|
|
752
|
-
tree = ast.parse(rendered)
|
|
753
|
-
except SyntaxError:
|
|
754
|
-
return self._check_comment_spec_text(req)
|
|
755
|
-
code = rendered
|
|
756
|
-
else:
|
|
757
|
-
# 分支 B·真非 Python 源码(Rust / JavaScript 模板):ast 无法解析。
|
|
758
|
-
# 改造前此处 `return []` = **静默跳过**,等于给多语言单元发
|
|
759
|
-
# 「假通过」——实测 RUST_UNITS/JS_UNITS 各 5 单元的 CCG 契约 0/5
|
|
760
|
-
# 达标,而 verifier 报「R3 缺 0」(`_ccg_stat` 留空、不进分母)
|
|
761
|
-
# → 审计报告完全隐身。
|
|
762
|
-
return self._check_comment_spec_text(req)
|
|
763
|
-
src_lines = code.splitlines()
|
|
764
|
-
_has_cn = lambda s: any('\u4e00' <= c <= '\u9fff' for c in s)
|
|
765
|
-
errs = []
|
|
766
|
-
# R1:函数/类中文注释
|
|
767
|
-
missing = []
|
|
768
|
-
for f in ast.walk(tree):
|
|
769
|
-
if not isinstance(f, (ast.FunctionDef, ast.AsyncFunctionDef,
|
|
770
|
-
ast.ClassDef)):
|
|
771
|
-
continue
|
|
772
|
-
doc = ast.get_docstring(f)
|
|
773
|
-
if doc and _has_cn(doc):
|
|
774
|
-
continue
|
|
775
|
-
ok = False
|
|
776
|
-
# 查 def/class 前后各 2 行内的 # 中文注释(docstring 已单独检查)
|
|
777
|
-
for ln in src_lines[max(0, f.lineno - 2):f.lineno + 3]:
|
|
778
|
-
if ln.strip().startswith('#') and _has_cn(ln):
|
|
779
|
-
ok = True
|
|
780
|
-
break
|
|
781
|
-
if not ok:
|
|
782
|
-
missing.append(f.name)
|
|
783
|
-
if missing:
|
|
784
|
-
errs.append(f"函数/类缺中文注释(条件论 R1): {missing[:5]}")
|
|
785
|
-
# R2:注释行中文语境
|
|
786
|
-
bad_lines = []
|
|
787
|
-
for i, ln in enumerate(src_lines, 1):
|
|
788
|
-
s = ln.strip()
|
|
789
|
-
if s.startswith('#') and not _has_cn(s) and len(s) > 3:
|
|
790
|
-
bad_lines.append(f"L{i}")
|
|
791
|
-
if bad_lines:
|
|
792
|
-
errs.append(f"纯英文注释行(条件论 R2,须中文说明): {bad_lines[:5]}")
|
|
793
|
-
# R3/R4:CCG 六要素注释——**默认软模式恒执行**(2026-09-17 修复)。
|
|
794
|
-
#
|
|
795
|
-
# 改造前的缺陷(「规范没有被执行」的机械根因,用户 2026-09-17 定性):
|
|
796
|
-
# ① R3/R4 被包在 `require_cond_comment` / `require_not_cond` 开关内,
|
|
797
|
-
# 而全仓**无任何调用方**传入这两个键 → 判据为**死代码**,恒不执行;
|
|
798
|
-
# ② 但 evidence 恒写「条件论注释通过」——R1/R2 通过即谎报六要素达标,
|
|
799
|
-
# 于是 code_solidified.json 留下「零标记却通过」的**伪证**;
|
|
800
|
-
# ③ 判据自身只查**首个** def、窗口只覆盖 def 前 2 行——与既有单元库
|
|
801
|
-
# 104+ 处标记的实际形态(`# 生效条件:` 紧跟 def 行之后)物理不相容。
|
|
802
|
-
# 现纪律:软模式**恒执行**并把达标率写入 evidence(诚实报告,不静默),
|
|
803
|
-
# 硬拦截仅在开关被显式传入时(全库升级完成后由调用方开启)。
|
|
804
|
-
r3_missing, total = self._cond_comment_missing(tree, src_lines)
|
|
805
|
-
if total:
|
|
806
|
-
self._comment_notes.append(
|
|
807
|
-
f"R3 三要素 {total - len(r3_missing)}/{total} 达标")
|
|
808
|
-
r4_missing, _ = self._not_cond_missing(tree, src_lines)
|
|
809
|
-
# 结构化留痕:软模式不拦截,若只把结论写进 evidence **字符串**,
|
|
810
|
-
# `--audit` 就无法机械汇总缺失清单——「规范没被执行」正是这样隐身的。
|
|
811
|
-
self._ccg_stat = {"r3_total": total, "r3_missing": r3_missing,
|
|
812
|
-
"r4_missing": r4_missing}
|
|
813
|
-
if r3_missing:
|
|
814
|
-
msg = f"条件论三要素注释缺失(R3): {r3_missing[:5]}"
|
|
815
|
-
if req.expected_structure.get("require_cond_comment"):
|
|
816
|
-
errs.append(msg)
|
|
817
|
-
else:
|
|
818
|
-
self._comment_notes.append(msg + "(软模式,不拦截)")
|
|
819
|
-
if r4_missing:
|
|
820
|
-
msg = f"不适用条件缺失(R4·代码语义条件协议): {r4_missing[:5]}"
|
|
821
|
-
if req.expected_structure.get("require_not_cond"):
|
|
822
|
-
errs.append(msg)
|
|
823
|
-
else:
|
|
824
|
-
self._comment_notes.append(msg + "(软模式,不拦截)")
|
|
825
|
-
return errs
|
|
826
|
-
|
|
827
|
-
def _render_placeholders(self, code: str, req: VerifyRequest) -> str:
|
|
828
|
-
"""展开单元模板占位符(`{fn}` → 具体标识符),使模板可被 ast 解析。
|
|
829
|
-
|
|
830
|
-
为什么需要:`*_UNITS` 的 pattern 是模板形态(`def {fn}(arr):`),
|
|
831
|
-
未经 `code_compose.compose_code` 替换前**不是合法 Python**——直接
|
|
832
|
-
ast.parse 必 SyntaxError,判据就会降级(v7 静默跳过 / v8 文本判据)。
|
|
833
|
-
展开后 template 与「运行时真实生成的代码」同形,R1/R2/R3/R4 全量判据
|
|
834
|
-
才真正作用于模板本体(v9)。
|
|
835
|
-
|
|
836
|
-
占位符名真源:`expected_structure["params"]`(调用方传单元声明的参数名),
|
|
837
|
-
缺省回退约定名 `("fn",)`(`code_compose` 第 17 行声明的模板约定)。
|
|
838
|
-
只替换**声明过的**占位符——不做正则通配,避免误伤 f-string / dict 字面量
|
|
839
|
-
(`{x}`、`{1: 1}` 形态)。
|
|
840
|
-
返回值与输入相同 = 无占位符可展开(调用方据此判定「真·非 Python」)。
|
|
841
|
-
"""
|
|
842
|
-
names = req.expected_structure.get("params") or ("fn",)
|
|
843
|
-
out = code
|
|
844
|
-
for p in names:
|
|
845
|
-
out = out.replace("{" + str(p) + "}", "solve")
|
|
846
|
-
return out
|
|
847
|
-
|
|
848
|
-
def _check_comment_spec_text(self, req: VerifyRequest) -> list:
|
|
849
|
-
"""非 Python 源码的 CCG 契约判据(Rust / JavaScript,`//` 或 `#` 注释)。
|
|
850
|
-
|
|
851
|
-
存在理由:`ast.parse` 对非 Python 源码抛 `SyntaxError`。改造前该分支直接
|
|
852
|
-
`return []` = **静默跳过**——多语言单元在校验器眼里永远「合规」,
|
|
853
|
-
`_ccg_stat` 留空不进分母,缺口在审计报告里完全隐身
|
|
854
|
-
(实测:verifier 报 R3 缺 0,文本实缺 5/5)。
|
|
855
|
-
|
|
856
|
-
判据与 Python 分支**同口径**(分层不重复):
|
|
857
|
-
· 入口符号 = 首个非空非注释行(Rust `fn {fn}(...)`、JS `function {fn}(...)`);
|
|
858
|
-
· 注释块 = 入口行之后的连续 `//`/`#` 行;
|
|
859
|
-
· R3 三要素(生效条件/子功能/执行)+ R4 不适用条件——软模式,恒执行;
|
|
860
|
-
· R1/R2 精神:注释块须含中文(缺失则硬拦截,与 Python 分支一致)。
|
|
861
|
-
"""
|
|
862
|
-
lines = req.code.splitlines()
|
|
863
|
-
entry = next((i for i, ln in enumerate(lines)
|
|
864
|
-
if ln.strip() and not ln.strip().startswith(("//", "#"))),
|
|
865
|
-
None)
|
|
866
|
-
if entry is None:
|
|
867
|
-
return []
|
|
868
|
-
block = []
|
|
869
|
-
for ln in lines[entry + 1:]:
|
|
870
|
-
s = ln.strip()
|
|
871
|
-
if s.startswith(("//", "#")):
|
|
872
|
-
block.append(s.lstrip("/#").strip())
|
|
873
|
-
elif not s:
|
|
874
|
-
continue
|
|
875
|
-
else:
|
|
876
|
-
break
|
|
877
|
-
joined = " ".join(block)
|
|
878
|
-
_has_cn = lambda s: any('\u4e00' <= c <= '\u9fff' for c in s)
|
|
879
|
-
errs = []
|
|
880
|
-
if not block or not _has_cn(joined):
|
|
881
|
-
errs.append(f"入口符号缺中文注释(条件论 R1/R2): L{entry + 1}")
|
|
882
|
-
need = ("生效条件", "子功能", "执行")
|
|
883
|
-
r3_missing = [k for k in need if k not in joined]
|
|
884
|
-
r4_missing = [] if "不适用条件" in joined else [f"L{entry + 1}"]
|
|
885
|
-
# 与 Python 分支同款结构化留痕(分母记 1 = 入口符号,不是「整个文件」)
|
|
886
|
-
self._ccg_stat = {"r3_total": 1, "r3_missing": r3_missing,
|
|
887
|
-
"r4_missing": r4_missing, "mode": "text"}
|
|
888
|
-
if r3_missing:
|
|
889
|
-
msg = f"条件论三要素注释缺失(R3·文本判据): {r3_missing}"
|
|
890
|
-
if req.expected_structure.get("require_cond_comment"):
|
|
891
|
-
errs.append(msg)
|
|
892
|
-
else:
|
|
893
|
-
self._comment_notes.append(msg + "(软模式,不拦截)")
|
|
894
|
-
if r4_missing:
|
|
895
|
-
msg = "不适用条件缺失(R4·文本判据)"
|
|
896
|
-
if req.expected_structure.get("require_not_cond"):
|
|
897
|
-
errs.append(msg)
|
|
898
|
-
else:
|
|
899
|
-
self._comment_notes.append(msg + "(软模式,不拦截)")
|
|
900
|
-
return errs
|
|
901
|
-
|
|
902
|
-
def _ccg_scope(self, tree) -> list:
|
|
903
|
-
"""R3/R4 共同判定面:CCG 注释的**检查对象** = 单元入口(唯一口径)。
|
|
904
|
-
|
|
905
|
-
三处判据校准(2026-09-17,依据 `--audit` 实测暴露的噪声):
|
|
906
|
-
① **只用 `tree.body` 顶层**——改造前用 `ast.walk`,会把**类内方法**
|
|
907
|
-
也展开计入(实测 `Cat` 与 `Cat.speak` 同现、`speak` 重复两次);
|
|
908
|
-
② **排除 `*_test` / `test_*` / `_` 前缀**——测试样例代码与私有辅助
|
|
909
|
-
不是契约对象(实测 `closure_test`/`gen_test`/`with_test` 等被计入分母);
|
|
910
|
-
③ **取首个合格符号 = 单元对外入口**——`*_units.py` 的既有约定:
|
|
911
|
-
pattern 首符号承载「何时可调 / 做什么 / 怎么用」,内部辅助
|
|
912
|
-
(dfs/walk/find/h1/up/down)无外部调用方,由 R1 中文注释覆盖。
|
|
913
|
-
与改造前注释声明的设计意图一致(「检查首个 def/class;内部辅助函数由
|
|
914
|
-
R1 覆盖」),修的是**窗口错位与谎报**,不是检查面。
|
|
915
|
-
"""
|
|
916
|
-
for n in getattr(tree, "body", []):
|
|
917
|
-
if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
|
918
|
-
nm = n.name
|
|
919
|
-
if (nm.startswith("_") or nm.endswith("_test")
|
|
920
|
-
or nm.startswith("test_")):
|
|
921
|
-
continue
|
|
922
|
-
return [n]
|
|
923
|
-
return []
|
|
924
|
-
|
|
925
|
-
def _ccg_block(self, f, src_lines) -> tuple:
|
|
926
|
-
"""读取单个符号的 CCG 注释块:返回 (注释行文本列表, docstring)。
|
|
927
|
-
|
|
928
|
-
窗口 = def/class 签名行之后、**函数体首个语句之前**的 `#` 注释
|
|
929
|
-
(多行签名的续行非 `#` 起始,自然跳过)+ docstring。
|
|
930
|
-
该窗口即既有单元库 104+ 处标记的实际物理位置(`# 生效条件:` 紧贴
|
|
931
|
-
def 行下一行);`f.body[0].lineno` 保证多行签名下窗口仍正确。
|
|
932
|
-
|
|
933
|
-
**唯一真源**(2026-09-17 裁决 A):窗口计算委托
|
|
934
|
-
`codeindex._body_comments`。历史问题:本函数曾自述「与
|
|
935
|
-
`codeindex._body_comments` 同款语义」,而该名当时**并不存在**——
|
|
936
|
-
悬空引用即第二份真相,已由本次收敛坐实。
|
|
937
|
-
|
|
938
|
-
兜底(第 7 条):本模块有三种加载形态——`-m md_cg.whitebox_kb.wisdom.verifier`、
|
|
939
|
-
顶层 `wisdom.verifier`(wheel 发布态,见 code_compose.py)、直跑
|
|
940
|
-
`python verifier.py`;后两者 import 不到 `md_cg.codeindex`,故保留
|
|
941
|
-
**逐字等价的兜底**,等价性由 `md_cg/test_codeindex.py` 机械守卫。
|
|
942
|
-
"""
|
|
943
|
-
body = getattr(f, "body", None)
|
|
944
|
-
body_lineno = body[0].lineno if body else f.lineno
|
|
945
|
-
raw = None
|
|
946
|
-
fn = _ci_body_comments()
|
|
947
|
-
if fn is not None:
|
|
948
|
-
raw = fn(src_lines, f.lineno, body_lineno)
|
|
949
|
-
if raw is None:
|
|
950
|
-
# 兜底(第 7 条):与唯一真源**逐字等价**,等价性由
|
|
951
|
-
# md_cg/test_codeindex.py 的委托等价断言机械守卫。
|
|
952
|
-
raw = [ln.strip()
|
|
953
|
-
for ln in src_lines[f.lineno:max(f.lineno, body_lineno - 1)]
|
|
954
|
-
if ln.strip().startswith("#")]
|
|
955
|
-
out = [s.lstrip("#").strip() for s in raw]
|
|
956
|
-
doc = (ast.get_docstring(f) or "").strip()
|
|
957
|
-
return out, doc
|
|
958
|
-
|
|
959
|
-
def _cond_comment_missing(self, tree, src_lines) -> tuple:
|
|
960
|
-
"""R3:缺「生效条件/子功能/执行」三要素的符号清单;返回 (missing, total)。
|
|
961
|
-
|
|
962
|
-
要素名真源 = `nodefile.CCG_CONTRACT_ROLES`(六要素接口契约);
|
|
963
|
-
R3 只抽其中三要素——功能名由 R1 覆盖、验证方式与不适用条件由
|
|
964
|
-
`codeindex.render` 落槽(分层不重复,改造前此三要素从未被检查过)。
|
|
965
|
-
"""
|
|
966
|
-
funcs = self._ccg_scope(tree)
|
|
967
|
-
need = ("生效条件", "子功能", "执行")
|
|
968
|
-
missing = []
|
|
969
|
-
for f in funcs:
|
|
970
|
-
block, doc = self._ccg_block(f, src_lines)
|
|
971
|
-
joined = " ".join(block) + " " + doc
|
|
972
|
-
if not all(k in joined for k in need):
|
|
973
|
-
missing.append(f.name)
|
|
974
|
-
return missing, len(funcs)
|
|
975
|
-
|
|
976
|
-
def _not_cond_missing(self, tree, src_lines) -> tuple:
|
|
977
|
-
"""R4:缺「不适用条件」的符号清单;返回 (missing, total)。
|
|
978
|
-
|
|
979
|
-
不适用条件 = 拒绝域 rejection_domain(何时**不能**调用)——
|
|
980
|
-
知道边界才敢调用,与「何时能调用」同等重要。
|
|
981
|
-
"""
|
|
982
|
-
funcs = self._ccg_scope(tree)
|
|
983
|
-
missing = []
|
|
984
|
-
for f in funcs:
|
|
985
|
-
block, doc = self._ccg_block(f, src_lines)
|
|
986
|
-
joined = " ".join(block) + " " + doc
|
|
987
|
-
if "不适用条件" not in joined:
|
|
988
|
-
missing.append(f.name)
|
|
989
|
-
return missing, len(funcs)
|
|
990
|
-
|
|
991
|
-
# ---------- ⑥ 集成测试 ----------
|
|
992
|
-
def _check_integration(self, req: VerifyRequest) -> Dict[str, Any]:
|
|
993
|
-
"""依赖单元组装 + 端到端运行(简化:deps 代码拼接后执行)。"""
|
|
994
|
-
if not req.deps:
|
|
995
|
-
return {"level": "集成", "ok": True, "evidence": "无依赖(跳过)"}
|
|
996
|
-
try:
|
|
997
|
-
# 组装依赖 + 被测代码
|
|
998
|
-
combined = "\n\n".join(req.deps) + "\n\n" + req.code
|
|
999
|
-
ns: Dict[str, Any] = {}
|
|
1000
|
-
exec(compile(combined, "<integrate>", "exec"), ns)
|
|
1001
|
-
|
|
1002
|
-
# 端到端:跑第一个样例
|
|
1003
|
-
if req.cases:
|
|
1004
|
-
func_name = None
|
|
1005
|
-
tree = ast.parse(req.code)
|
|
1006
|
-
funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
|
|
1007
|
-
func_name = funcs[0].name if funcs else None
|
|
1008
|
-
if func_name and func_name in ns:
|
|
1009
|
-
inp, exp = req.cases[0]
|
|
1010
|
-
got = ns[func_name](inp) if not isinstance(inp, tuple) else ns[func_name](*inp)
|
|
1011
|
-
if got != exp:
|
|
1012
|
-
return {"level": "集成", "ok": False,
|
|
1013
|
-
"evidence": f"组装后首样例失败: {inp} → {got}(期望 {exp})"}
|
|
1014
|
-
return {"level": "集成", "ok": True,
|
|
1015
|
-
"evidence": f"{len(req.deps)} 个依赖组装 + 端到端通过"}
|
|
1016
|
-
except Exception as e:
|
|
1017
|
-
return {"level": "集成", "ok": False,
|
|
1018
|
-
"evidence": f"组装失败: {e}"}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
# ============ 四、CLI ============
|
|
1022
|
-
|
|
1023
|
-
def _build_request_from_file(path: str) -> VerifyRequest:
|
|
1024
|
-
"""从 JSON 文件构建校验请求。"""
|
|
1025
|
-
with open(path, "r", encoding="utf-8") as f:
|
|
1026
|
-
data = json.load(f)
|
|
1027
|
-
return VerifyRequest(
|
|
1028
|
-
task=data.get("task", ""),
|
|
1029
|
-
code=data.get("code", ""),
|
|
1030
|
-
unit_id=data.get("unit_id", ""),
|
|
1031
|
-
lang=data.get("lang", "python"),
|
|
1032
|
-
cases=[tuple(c) for c in data.get("cases", [])],
|
|
1033
|
-
deps=data.get("deps", []),
|
|
1034
|
-
expected_structure=data.get("structure", {}),
|
|
1035
|
-
)
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
def _self_test() -> bool:
|
|
1039
|
-
"""自测:排序单元应通过,错误代码应失败。"""
|
|
1040
|
-
v = Verifier()
|
|
1041
|
-
passed = 0
|
|
1042
|
-
|
|
1043
|
-
# 正确代码(含中文注释——条件论注释规范 R1)
|
|
1044
|
-
good_code = (
|
|
1045
|
-
"def solve(arr):\n"
|
|
1046
|
-
" # 排序:冒泡法(相邻比较交换,最小元素浮到头部)\n"
|
|
1047
|
-
" n = len(arr)\n"
|
|
1048
|
-
" for i in range(n):\n"
|
|
1049
|
-
" for j in range(n-1-i):\n"
|
|
1050
|
-
" if arr[j] > arr[j+1]:\n"
|
|
1051
|
-
" arr[j], arr[j+1] = arr[j+1], arr[j]\n"
|
|
1052
|
-
" return arr\n"
|
|
1053
|
-
)
|
|
1054
|
-
r1 = v.verify(VerifyRequest(
|
|
1055
|
-
task="排序", code=good_code, unit_id="test_sort",
|
|
1056
|
-
cases=[([3, 1, 2], [1, 2, 3]), ([], []), ([1], [1])],
|
|
1057
|
-
))
|
|
1058
|
-
print(f"[1] 排序正确代码: {r1.summary()}")
|
|
1059
|
-
passed += 1 if r1.ok else 0
|
|
1060
|
-
|
|
1061
|
-
# 错误代码(冒泡逻辑反了)
|
|
1062
|
-
bad_code = good_code.replace("if arr[j] > arr[j+1]", "if arr[j] < arr[j+1]")
|
|
1063
|
-
r2 = v.verify(VerifyRequest(
|
|
1064
|
-
task="排序", code=bad_code, unit_id="test_sort_bad",
|
|
1065
|
-
cases=[([3, 1, 2], [1, 2, 3]), ([], []), ([1], [1])],
|
|
1066
|
-
))
|
|
1067
|
-
print(f"[2] 排序错误代码: {r2.summary()}")
|
|
1068
|
-
passed += 1 if not r2.ok else 0
|
|
1069
|
-
|
|
1070
|
-
# 缓存命中
|
|
1071
|
-
r3 = v.verify(VerifyRequest(
|
|
1072
|
-
task="排序", code=good_code, unit_id="test_sort",
|
|
1073
|
-
cases=[([3, 1, 2], [1, 2, 3]), ([], []), ([1], [1])],
|
|
1074
|
-
))
|
|
1075
|
-
print(f"[3] 缓存命中: cached={r3.cached} {r3.summary()}")
|
|
1076
|
-
passed += 1 if r3.cached else 0
|
|
1077
|
-
|
|
1078
|
-
# 语法错误
|
|
1079
|
-
r4 = v.verify(VerifyRequest(
|
|
1080
|
-
task="排序", code="def solve(arr):\n return ", unit_id="test_syntax",
|
|
1081
|
-
cases=[([1], [1])],
|
|
1082
|
-
))
|
|
1083
|
-
print(f"[4] 语法错误: {r4.summary()}")
|
|
1084
|
-
passed += 1 if not r4.ok else 0
|
|
1085
|
-
|
|
1086
|
-
# 规范符合性(排序但无比较结构)
|
|
1087
|
-
r5 = v.verify(VerifyRequest(
|
|
1088
|
-
task="排序", code="def solve(arr):\n return arr", unit_id="test_nosort",
|
|
1089
|
-
cases=[([3, 1, 2], [3, 1, 2])],
|
|
1090
|
-
))
|
|
1091
|
-
print(f"[5] 规范不符(排序无比较): {r5.summary()}")
|
|
1092
|
-
passed += 1 if not r5.ok else 0
|
|
1093
|
-
|
|
1094
|
-
print(f"\n自测: {passed}/5 通过")
|
|
1095
|
-
return passed == 5
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
def _audit_all() -> None:
|
|
1099
|
-
"""全量审计:六域单元库 → 本地校验器 → 报告(通过率/失败清单/缓存/耗时)。"""
|
|
1100
|
-
from compiler_code_units import COMPILER_UNITS
|
|
1101
|
-
from python_code_units import PYTHON_UNITS
|
|
1102
|
-
from graph_db_units import GRAPH_UNITS
|
|
1103
|
-
from os_units import OS_UNITS
|
|
1104
|
-
from browser_units import BROWSER_UNITS
|
|
1105
|
-
from net_units import NET_UNITS
|
|
1106
|
-
domains = [('compiler', COMPILER_UNITS), ('pylang', PYTHON_UNITS),
|
|
1107
|
-
('graph', GRAPH_UNITS), ('os', OS_UNITS),
|
|
1108
|
-
('browser', BROWSER_UNITS), ('net', NET_UNITS)]
|
|
1109
|
-
# 语言单元表(白箱自举主干):改造前**不在审计面**——`_audit_all` 只遍历六域
|
|
1110
|
-
# `*_units.py`,而模板生产的 CODE_UNITS/RUST_UNITS/JS_UNITS 从不进报告。
|
|
1111
|
-
# 「规范没被执行」正是这样隐身的:生成端零契约 + 审计端不看它。
|
|
1112
|
-
from code_compose import CODE_UNITS, RUST_UNITS, JS_UNITS
|
|
1113
|
-
lang_domains = [('code-py', CODE_UNITS), ('code-rust', RUST_UNITS),
|
|
1114
|
-
('code-js', JS_UNITS)]
|
|
1115
|
-
t0 = time.time()
|
|
1116
|
-
v = Verifier()
|
|
1117
|
-
total = ok = 0
|
|
1118
|
-
fails = []
|
|
1119
|
-
by_domain = {}
|
|
1120
|
-
ccg_total = ccg_missing = 0
|
|
1121
|
-
ccg_gaps = [] # [(uid, r3_missing, r4_missing)] —— 软模式缺失清单
|
|
1122
|
-
for dname, units in domains:
|
|
1123
|
-
d_ok = d_fail = 0
|
|
1124
|
-
for uid, u in units.items():
|
|
1125
|
-
total += 1
|
|
1126
|
-
req = VerifyRequest(
|
|
1127
|
-
task=u['task'], code=u['pattern'], unit_id=uid,
|
|
1128
|
-
cases=list(u.get('cases', [])),
|
|
1129
|
-
expected_structure={'inject': True} if u.get('needs_inject') else {})
|
|
1130
|
-
r = v.verify(req)
|
|
1131
|
-
# CCG 六要素(软模式)汇总:R3/R4 缺失**不拦截**,故不能只看 r.ok——
|
|
1132
|
-
# 必须单独取「规范符合性」check 里的结构化 ccg 块;否则「规范未执行」
|
|
1133
|
-
# 会再次隐身(改造前 evidence 恒写「条件论注释通过」,无从发现)。
|
|
1134
|
-
spec = next((c for c in r.checks if isinstance(c, dict)
|
|
1135
|
-
and c.get("level") == "规范符合性"), None)
|
|
1136
|
-
st = (spec or {}).get("ccg") or {}
|
|
1137
|
-
if st.get("r3_total"):
|
|
1138
|
-
ccg_total += st["r3_total"]
|
|
1139
|
-
ccg_missing += len(st.get("r3_missing") or [])
|
|
1140
|
-
if st.get("r3_missing") or st.get("r4_missing"):
|
|
1141
|
-
ccg_gaps.append((uid, st.get("r3_missing") or [],
|
|
1142
|
-
st.get("r4_missing") or []))
|
|
1143
|
-
if r.ok:
|
|
1144
|
-
ok += 1; d_ok += 1
|
|
1145
|
-
else:
|
|
1146
|
-
fails.append((uid, r.reason[:80])); d_fail += 1
|
|
1147
|
-
by_domain[dname] = (d_ok, d_fail)
|
|
1148
|
-
s = v.cache.stats()
|
|
1149
|
-
print(f"=== 全量审计(verifier v{VERIFIER_VERSION}): {ok}/{total} 通过 "
|
|
1150
|
-
f"({100.0 * ok / total:.1f}%) 耗时 {time.time() - t0:.1f}s ===")
|
|
1151
|
-
print("各域:", by_domain)
|
|
1152
|
-
# CCG 六要素报告必须**显式打印**:单元"校验通过"只代表 L1-L3 + R1/R2 过了,
|
|
1153
|
-
# 六要素接口契约是否达成是另一件事——两者混为一谈正是问题根源。
|
|
1154
|
-
rate = (100.0 * (ccg_total - ccg_missing) / ccg_total) if ccg_total else 0.0
|
|
1155
|
-
print(f"CCG 三要素(R3 软模式): {ccg_total - ccg_missing}/{ccg_total} 符号达标"
|
|
1156
|
-
f"({rate:.1f}%) | 有缺失的单元 {len(ccg_gaps)}/{total} 个")
|
|
1157
|
-
if ccg_gaps:
|
|
1158
|
-
print("--- CCG 注释缺失清单(软模式,不拦截)---")
|
|
1159
|
-
for uid, m3, m4 in ccg_gaps:
|
|
1160
|
-
print(f"[{uid}] R3缺={m3[:6]} R4缺={m4[:4]}")
|
|
1161
|
-
# --- 语言单元表(自举主干)CCG 契约审计 ---
|
|
1162
|
-
# 与六域**分开统计**,原因有二:
|
|
1163
|
-
# ① RUST/JS 模板非 Python 语法,L1 语法必失败属正常(校验器与目标语言
|
|
1164
|
-
# 不匹配),此处只问「契约是否写进模板」——走文本判据
|
|
1165
|
-
# (`_check_comment_spec_text`,mode="text");
|
|
1166
|
-
# ② CODE_UNITS 是 Python 模板但含 `{fn}` 占位符(未替换前非法 Python),
|
|
1167
|
-
# v9 起由 `_render_placeholders` 展开后走**全量 Python 判据**
|
|
1168
|
-
# (mode="python":R1 逐符号中文注释 + R2 注释语境 + R3/R4),
|
|
1169
|
-
# 故本节 `params` 必须透传单元声明的占位符名。
|
|
1170
|
-
# 两档判据强度不同但**达标口径一致**(三要素 + 不适用条件齐备)。
|
|
1171
|
-
lang_total = lang_ok = 0
|
|
1172
|
-
lang_gaps = []
|
|
1173
|
-
lang_modes = {} # 判据档位计数(python=全量判据 / text=文本判据)
|
|
1174
|
-
for dname, units in lang_domains:
|
|
1175
|
-
for uid, u in units.items():
|
|
1176
|
-
lang_total += 1
|
|
1177
|
-
r = v.verify(VerifyRequest(task=u.get('task', ''), code=u['pattern'],
|
|
1178
|
-
unit_id=uid, cases=[],
|
|
1179
|
-
expected_structure={
|
|
1180
|
-
'params': u.get('params') or ['fn']}))
|
|
1181
|
-
spec = next((c for c in r.checks if isinstance(c, dict)
|
|
1182
|
-
and c.get("level") == "规范符合性"), None) or {}
|
|
1183
|
-
st = spec.get("ccg") or {}
|
|
1184
|
-
r3m = st.get("r3_missing") or []
|
|
1185
|
-
r4m = st.get("r4_missing") or []
|
|
1186
|
-
md = st.get("mode", "python")
|
|
1187
|
-
lang_modes[md] = lang_modes.get(md, 0) + 1
|
|
1188
|
-
# 「达标」= 判据产出 + 无硬失败 + 契约齐备,三者缺一不可——
|
|
1189
|
-
# 只统计 ccg 缺失会漏报 R1/R2 硬失败(spec.ok=False 但 ccg 齐备
|
|
1190
|
-
# 仍会被算作达标)= 新的谎报面(同 v7「没检查冒充检查通过」同构)。
|
|
1191
|
-
if not st.get("r3_total"):
|
|
1192
|
-
lang_gaps.append((dname, uid, md, "判据未产出(ccg 块为空)"))
|
|
1193
|
-
elif not spec.get("ok"):
|
|
1194
|
-
lang_gaps.append((dname, uid, md,
|
|
1195
|
-
f"硬失败: {str(spec.get('evidence'))[:90]}"))
|
|
1196
|
-
elif r3m or r4m:
|
|
1197
|
-
lang_gaps.append((dname, uid, md, f"R3缺={r3m} R4缺={r4m}"))
|
|
1198
|
-
else:
|
|
1199
|
-
lang_ok += 1
|
|
1200
|
-
lrate = (100.0 * lang_ok / lang_total) if lang_total else 0.0
|
|
1201
|
-
print(f"=== 语言单元表 CCG 契约: {lang_ok}/{lang_total} 达标({lrate:.1f}%)"
|
|
1202
|
-
f" | 判据档位 {lang_modes}(python=展开占位符后全量判据 / text=文本判据)===")
|
|
1203
|
-
for dname, uid, md, desc in lang_gaps:
|
|
1204
|
-
print(f" [{dname}/{uid}] mode={md} {desc}")
|
|
1205
|
-
print(f"缓存: 共 {s['total']} 条(通过 {s['passed']} / 失败 {s['failed']})"
|
|
1206
|
-
f" | 本进程命中 {s['hits']} / 未命中 {s['misses']}(命中率 {s['hit_rate']}%)")
|
|
1207
|
-
if fails:
|
|
1208
|
-
print("--- 失败清单 ---")
|
|
1209
|
-
for uid, reason in fails:
|
|
1210
|
-
print(f"[{uid}] {reason}")
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
def _cost_model(daily_tokens: float = 1.3e9, hit_rate: float = 0.999,
|
|
1214
|
-
price_hit: float = 2.0, price_full: float = 2.0,
|
|
1215
|
-
days: int = 30) -> None:
|
|
1216
|
-
"""本地化成本对照模型(阶段 5 替换测算器)。
|
|
1217
|
-
|
|
1218
|
-
「原 LLM 校验环节」= 白箱自举的外部校准角色(LLM 查表校验):白箱自举
|
|
1219
|
-
总消耗 13 亿 token(用户实测),其中 99.9% 是重复查表校验(反复读同样
|
|
1220
|
-
的单元/代码/规则表——即 LLM 缓存命中)。本地校验器(六层确定性规则 +
|
|
1221
|
-
sha256 指纹缓存)接管后,这部分 token 归零;仅剩 0.1% 为真·新任务
|
|
1222
|
-
(新单元设计/语义校准)保留 LLM 价值。
|
|
1223
|
-
|
|
1224
|
-
默认参数取自设计文档实测:13 亿 token/天、99.9% 缓存命中、GLM 缓存价
|
|
1225
|
-
2 元/百万。原方案:全部 token 经 LLM(命中按缓存价计费);
|
|
1226
|
-
本地化后:命中部分走本地磁盘(≈0 成本),仅未命中部分保留 LLM 兜底。
|
|
1227
|
-
"""
|
|
1228
|
-
per_m = 1e6
|
|
1229
|
-
orig = daily_tokens * (hit_rate * price_hit + (1 - hit_rate) * price_full) / per_m
|
|
1230
|
-
local = daily_tokens * (1 - hit_rate) * price_full / per_m
|
|
1231
|
-
saved = orig - local
|
|
1232
|
-
print(f"=== 本地化成本对照(LLM 查表校验 → 本地校验缓存)===")
|
|
1233
|
-
print(f"背景: 白箱自举外部校准(LLM 查表校验)日消耗 {daily_tokens:.3g} token,"
|
|
1234
|
-
f"其中 {hit_rate*100:.1f}% 为重复查表(缓存命中)")
|
|
1235
|
-
print(f"输入: 每日 token {daily_tokens:.3g} | 命中率 {hit_rate*100:.1f}% "
|
|
1236
|
-
f"| 命中价 {price_hit} 元/百万 | 未命中价 {price_full} 元/百万")
|
|
1237
|
-
print(f"原方案(LLM 缓存命中付费): {orig:,.2f} 元/天 ≈ {orig*days:,.0f} 元/{days}天")
|
|
1238
|
-
print(f"本地化后(命中零成本,仅未命中 LLM 兜底): {local:,.2f} 元/天 "
|
|
1239
|
-
f"≈ {local*days:,.0f} 元/{days}天")
|
|
1240
|
-
print(f"节省: {saved:,.2f} 元/天 ≈ {saved*days:,.0f} 元/{days}天 "
|
|
1241
|
-
f"(省 {(100*saved/orig if orig else 0):.1f}%)")
|
|
1242
|
-
print(f"对照: 白箱自举 {daily_tokens:.3g} token 中 "
|
|
1243
|
-
f"{daily_tokens*hit_rate:.3g} token({hit_rate*100:.1f}%)为重复查表校验,"
|
|
1244
|
-
f"由 verifier 本地缓存接管后 token 归零")
|
|
1245
|
-
if hit_rate == 1.0:
|
|
1246
|
-
print("命中率 100% → 本地化后每日成本归零(磁盘 I/O 可忽略)")
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
def main() -> None:
|
|
1250
|
-
import argparse
|
|
1251
|
-
ap = argparse.ArgumentParser(description="白箱本地校验器(零 LLM)")
|
|
1252
|
-
ap.add_argument("--verify", type=str, help="校验请求 JSON 文件路径")
|
|
1253
|
-
ap.add_argument("--cache-stats", action="store_true", help="缓存统计")
|
|
1254
|
-
ap.add_argument("--audit", action="store_true", help="全量审计(六域单元)")
|
|
1255
|
-
ap.add_argument("--cost-model", action="store_true", help="本地化成本对照模型")
|
|
1256
|
-
ap.add_argument("--tokens", type=float, default=1.3e9, help="每日 token 量")
|
|
1257
|
-
ap.add_argument("--hit-rate", type=float, default=0.999, help="缓存命中率")
|
|
1258
|
-
ap.add_argument("--price-hit", type=float, default=2.0, help="命中价 元/百万")
|
|
1259
|
-
ap.add_argument("--price-full", type=float, default=2.0, help="未命中价 元/百万")
|
|
1260
|
-
ap.add_argument("--days", type=int, default=30, help="测算天数")
|
|
1261
|
-
ap.add_argument("--self-test", action="store_true", help="自测")
|
|
1262
|
-
args = ap.parse_args()
|
|
1263
|
-
|
|
1264
|
-
if args.self_test:
|
|
1265
|
-
sys.exit(0 if _self_test() else 1)
|
|
1266
|
-
|
|
1267
|
-
if args.cost_model:
|
|
1268
|
-
_cost_model(args.tokens, args.hit_rate, args.price_hit,
|
|
1269
|
-
args.price_full, args.days)
|
|
1270
|
-
return
|
|
1271
|
-
|
|
1272
|
-
if args.audit:
|
|
1273
|
-
_audit_all()
|
|
1274
|
-
return
|
|
1275
|
-
|
|
1276
|
-
if args.cache_stats:
|
|
1277
|
-
stats = VerifyCache().stats()
|
|
1278
|
-
sv = VerifyCache().savings()
|
|
1279
|
-
print(f"缓存统计: 共 {stats['total']} 条(通过 {stats['passed']} / 失败 {stats['failed']})")
|
|
1280
|
-
print(f"命中计数: 本进程命中 {stats['hits']} / 未命中 {stats['misses']}(命中率 {stats['hit_rate']}%)")
|
|
1281
|
-
print(f"审计日志: {sv['requests']} 次校验(缓存命中 {sv['hits']} 次)")
|
|
1282
|
-
print(f"减少的 LLM 输入: 累计 {sv['saved_chars']:,} 字符"
|
|
1283
|
-
f" ≈ {sv['saved_tokens']:,} token(表+请求内容经本地处理,不再输入 LLM)")
|
|
1284
|
-
print("(每次命中 = 一次本该发生的 LLM 查表 → 本地零计算返回)")
|
|
1285
|
-
return
|
|
1286
|
-
|
|
1287
|
-
if args.verify:
|
|
1288
|
-
req = _build_request_from_file(args.verify)
|
|
1289
|
-
result = Verifier().verify(req)
|
|
1290
|
-
print(json.dumps(asdict(result), ensure_ascii=False, indent=1))
|
|
1291
|
-
return
|
|
1292
|
-
|
|
1293
|
-
ap.print_help()
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
if __name__ == "__main__":
|
|
1297
|
-
main()
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""verifier.py · 白箱本地校验器(Zero-LLM Verifier)v1
|
|
3
|
+
|
|
4
|
+
目标:把「白箱写代码 → 外部校验(是否符合规范/能否执行)」全部本地化,
|
|
5
|
+
彻底抛开大模型依赖。重复校验走本地缓存(替代 99.9% 的 LLM 缓存命中)。
|
|
6
|
+
|
|
7
|
+
设计:docs/白箱本地校验器_ZeroLLM_Verifier.md
|
|
8
|
+
|
|
9
|
+
校验链(六层,全部确定性):
|
|
10
|
+
① 校验指纹 → 本地缓存(已校验过 → 直接返回)
|
|
11
|
+
② L1 语法:ast.parse + 结构规则
|
|
12
|
+
③ L2 样例:输入→期望断言运行(物理基底裁决)
|
|
13
|
+
④ L3 边界:额外边界用例
|
|
14
|
+
⑤ 规范符合性:condition_kb 语义对齐 + 结构规范(白箱规则)
|
|
15
|
+
⑥ 集成测试:依赖单元组装 + 回归
|
|
16
|
+
|
|
17
|
+
用法:
|
|
18
|
+
python verifier.py --verify "os_112|工作窃取|<code文件路径>" # 校验单个
|
|
19
|
+
python verifier.py --cache-stats # 缓存统计
|
|
20
|
+
python verifier.py --self-test # 自测
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import ast
|
|
25
|
+
import copy
|
|
26
|
+
import hashlib
|
|
27
|
+
import importlib
|
|
28
|
+
import json
|
|
29
|
+
import os
|
|
30
|
+
import subprocess
|
|
31
|
+
import sys
|
|
32
|
+
import tempfile
|
|
33
|
+
import time
|
|
34
|
+
import traceback
|
|
35
|
+
import types
|
|
36
|
+
from dataclasses import dataclass, field, asdict
|
|
37
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
38
|
+
|
|
39
|
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
40
|
+
sys.path.insert(0, HERE)
|
|
41
|
+
|
|
42
|
+
CACHE_FILE = os.path.join(HERE, "..", "data", "verify_cache.json")
|
|
43
|
+
SAVINGS_LOG = os.path.join(HERE, "..", "data", "verify_savings.jsonl")
|
|
44
|
+
DATA_DIR = os.path.join(HERE, "..", "data")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ============ 一、数据结构 ============
|
|
48
|
+
|
|
49
|
+
def _stable_serialize(obj: Any) -> Any:
|
|
50
|
+
"""任意类型确定性序列化(缓存键基础)。
|
|
51
|
+
|
|
52
|
+
cases 里可能出现 set/bytes/lambda 等 json 不可序列化类型(如集合推导的
|
|
53
|
+
{1,2,3} 与 lambda、帧解析的 bytes)——指纹必须对它们稳定:
|
|
54
|
+
set/frozenset → 元素 str 排序化(去重保稳定)
|
|
55
|
+
bytes/bytearray → hex
|
|
56
|
+
callable(lambda/函数)→ __code__ 确定性摘要(co_code+常量+名,跨进程稳定,
|
|
57
|
+
不含内存地址——lambda 的 repr 含地址会导致重启后指纹漂移、缓存失效)
|
|
58
|
+
其余 → repr 回退(域单元 cases 无自定义类实例,足够)。
|
|
59
|
+
"""
|
|
60
|
+
if obj is None or isinstance(obj, (bool, int, float, str)):
|
|
61
|
+
return obj
|
|
62
|
+
if isinstance(obj, (list, tuple)):
|
|
63
|
+
return [_stable_serialize(x) for x in obj]
|
|
64
|
+
if isinstance(obj, (set, frozenset)):
|
|
65
|
+
return {"__set__": sorted(str(_stable_serialize(x)) for x in obj)}
|
|
66
|
+
if isinstance(obj, dict):
|
|
67
|
+
return {"__dict__": {str(k): _stable_serialize(v)
|
|
68
|
+
for k, v in sorted(obj.items(), key=lambda kv: str(kv[0]))}}
|
|
69
|
+
if isinstance(obj, (bytes, bytearray)):
|
|
70
|
+
return {"__bytes__": bytes(obj).hex()}
|
|
71
|
+
if isinstance(obj, types.CodeType):
|
|
72
|
+
# 嵌套 code 对象(lambda 的 co_consts 里可能含 <listcomp>/嵌套 lambda):
|
|
73
|
+
# repr 含内存地址(<code object at 0x…>)跨进程漂移——必须确定性摘要
|
|
74
|
+
digest = hashlib.sha256(json.dumps(
|
|
75
|
+
[obj.co_code.hex(), list(obj.co_consts), list(obj.co_names)],
|
|
76
|
+
ensure_ascii=False, sort_keys=True, default=_stable_serialize,
|
|
77
|
+
).encode("utf-8")).hexdigest()[:16]
|
|
78
|
+
return {"__code__": digest}
|
|
79
|
+
if callable(obj):
|
|
80
|
+
co = getattr(obj, "__code__", None)
|
|
81
|
+
if co is not None:
|
|
82
|
+
digest = hashlib.sha256(json.dumps(
|
|
83
|
+
[co.co_code.hex(), list(co.co_consts), list(co.co_names)],
|
|
84
|
+
ensure_ascii=False, sort_keys=True, default=_stable_serialize,
|
|
85
|
+
).encode("utf-8")).hexdigest()[:16]
|
|
86
|
+
return {"__fn__": digest}
|
|
87
|
+
return {"__fn__": repr(obj)}
|
|
88
|
+
try:
|
|
89
|
+
return {"__obj__": repr(obj)}
|
|
90
|
+
except Exception:
|
|
91
|
+
return {"__obj__": type(obj).__name__}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _assert_match(got: Any, exp: Any) -> bool:
|
|
95
|
+
"""结果断言:dict 期望做子集匹配(got 包含 exp 全部键值即可)。
|
|
96
|
+
|
|
97
|
+
白箱单元常返回完整状态字典(如 VM 执行循环返回
|
|
98
|
+
{'trust':…, 'symbols':…, 'cond':…, 'stack':…}),而样例期望只关心其中
|
|
99
|
+
关键字段(如 {'trust': 0.8})——完整相等会误判,子集匹配符合「状态断言」语义。
|
|
100
|
+
"""
|
|
101
|
+
if isinstance(exp, dict) and isinstance(got, dict):
|
|
102
|
+
return all(got.get(k) == v for k, v in exp.items())
|
|
103
|
+
return got == exp
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass
|
|
107
|
+
class VerifyRequest:
|
|
108
|
+
"""校验请求:白箱生成的代码 + 单元定义。
|
|
109
|
+
|
|
110
|
+
cases/deps 做深拷贝(不可变快照)——被校验代码可能原地修改输入
|
|
111
|
+
(如排序 arr[j], arr[j+1] = ...),若引用外部共享 list 会污染原始数据,
|
|
112
|
+
导致相同请求产生不同指纹(缓存失效)。
|
|
113
|
+
"""
|
|
114
|
+
task: str # 任务描述(排序/循环编译/图遍历…)
|
|
115
|
+
code: str # 白箱生成的代码
|
|
116
|
+
unit_id: str = "" # 单元标识(域+序号,如 os_112)
|
|
117
|
+
lang: str = "python" # 语言
|
|
118
|
+
cases: List[Tuple[Any, Any]] = field(default_factory=list) # (输入, 期望)
|
|
119
|
+
deps: List[str] = field(default_factory=list) # 依赖单元
|
|
120
|
+
expected_structure: Dict[str, Any] = field(default_factory=dict) # 规范约束
|
|
121
|
+
|
|
122
|
+
def __post_init__(self) -> None:
|
|
123
|
+
"""深拷贝防污染 + 指纹预计算(外部原地修改会让指纹漂移·缓存错配——P0 纪律)。"""
|
|
124
|
+
# 深拷贝可变字段——防止被校验代码原地修改污染外部共享对象
|
|
125
|
+
self.cases = copy.deepcopy(self.cases)
|
|
126
|
+
self.deps = list(self.deps)
|
|
127
|
+
self.expected_structure = dict(self.expected_structure)
|
|
128
|
+
|
|
129
|
+
def fingerprint(self) -> str:
|
|
130
|
+
"""确定性指纹:相同请求 → 相同指纹(缓存键)。
|
|
131
|
+
|
|
132
|
+
先整体经 _stable_serialize 归一化再 dumps——json.dumps 的 default 钩子
|
|
133
|
+
只处理值、不处理 dict 键(tuple 键如事件委托的 {('btn','click'):...}
|
|
134
|
+
直接崩溃),故必须把整个结构(含键)先递归归一化。
|
|
135
|
+
"""
|
|
136
|
+
payload = json.dumps(_stable_serialize({
|
|
137
|
+
"task": self.task, "code": self.code, "unit_id": self.unit_id,
|
|
138
|
+
"lang": self.lang, "cases": self.cases, "deps": self.deps,
|
|
139
|
+
"structure": self.expected_structure,
|
|
140
|
+
}), ensure_ascii=False, sort_keys=True)
|
|
141
|
+
return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:32]
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@dataclass
|
|
145
|
+
class VerifyResult:
|
|
146
|
+
"""校验结果。"""
|
|
147
|
+
ok: bool
|
|
148
|
+
checks: List[Dict[str, Any]] = field(default_factory=list) # [{level, ok, evidence}]
|
|
149
|
+
fingerprint: str = ""
|
|
150
|
+
cached: bool = False
|
|
151
|
+
reason: str = ""
|
|
152
|
+
verified_at: str = "" # 校验时间戳(ISO)
|
|
153
|
+
version: int = 0 # 校验器规则版本(VERIFIER_VERSION 快照)
|
|
154
|
+
|
|
155
|
+
def summary(self) -> str:
|
|
156
|
+
parts = [f"✅ 通过" if self.ok else f"❌ 失败"]
|
|
157
|
+
for c in self.checks:
|
|
158
|
+
mark = "✓" if c["ok"] else "✗"
|
|
159
|
+
parts.append(f"{mark} {c['level']}")
|
|
160
|
+
if self.reason:
|
|
161
|
+
parts.append(f"原因: {self.reason}")
|
|
162
|
+
if self.version:
|
|
163
|
+
parts.append(f"v{self.version}")
|
|
164
|
+
return " | ".join(parts)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# ============ 二、本地缓存(替代 LLM 缓存命中) ============
|
|
168
|
+
|
|
169
|
+
# 校验器规则版本:L1/L2/L3/规范/集成规则升级时必须 +1——
|
|
170
|
+
# 旧版本缓存结果在规则已变的场景下不再可信,强制全部失效重验
|
|
171
|
+
# (等价于「协议升级 → 相关缓存刷新」,GLM 建议的 protocol_version 落地)。
|
|
172
|
+
# v6(2026-08-31):cases 形态修复(list→tuple)后指纹归一化同值,旧 False
|
|
173
|
+
# 条目对修复免疫;bump 版本作废全部旧缓存——长驻进程内存快照复活毒条目的
|
|
174
|
+
# 终局解(版本不符整体清空,任何进程加载即重建)。
|
|
175
|
+
VERIFIER_VERSION = 10 # v10:结构规范的字面匹配改大小写不敏感——该表是语言无关的
|
|
176
|
+
# 任务语义判据(要求去重结构出现),字面取自 Python 命名习惯
|
|
177
|
+
# (set/seen),JS 标准 API 写作 `new Set(arr)`,敏感匹配把正确
|
|
178
|
+
# 实现判为「未见对应结构」(实测 code-js/去重 假失败)。v9:
|
|
179
|
+
# Python 模板(含 {fn} 占位符)先展开占位符再走 Python
|
|
180
|
+
# 判据——逐字复用 _render_placeholders(模板不再降级为文本判据,
|
|
181
|
+
# R1/R2/R3/R4 全量生效)。v8:非 Python 源码改走文本判据(Rust/JS
|
|
182
|
+
# 不再静默跳过假通过)+ `_audit_all` 审计面纳入语言单元表;
|
|
183
|
+
# v7:R3/R4 由死代码转为软模式恒执行 + 判据校准(入口/窗口/顶层)
|
|
184
|
+
# + evidence 诚实化——判据变更必须 bump,否则旧判定被缓存复用
|
|
185
|
+
_CACHE_VERSION_KEY = "_verifier_version"
|
|
186
|
+
_STATS_KEY = "_stats"
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def estimate_tokens(text: str) -> int:
|
|
190
|
+
"""确定性 token 估算(用于「减少的 LLM 输入」度量,非精确计费)。
|
|
191
|
+
|
|
192
|
+
中文混合内容:中文字符按 1.2 token/字(中文 1 字 ≈ 1-1.5 token),
|
|
193
|
+
其余(代码/英文/标点)按 0.3 token/字符(英文/代码 1 token ≈ 3-4 字符)。
|
|
194
|
+
"""
|
|
195
|
+
cjk = sum(1 for ch in text if '\u4e00' <= ch <= '\u9fff')
|
|
196
|
+
other = len(text) - cjk
|
|
197
|
+
return int(cjk * 1.2 + other * 0.3)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _table_payload_chars() -> int:
|
|
201
|
+
"""「整张表」内容量:六域单元库定义(pattern+cases)总字符数。
|
|
202
|
+
|
|
203
|
+
设计文档背景:原方案「每次把整张表塞进 LLM 上下文查一次」——
|
|
204
|
+
表 = 白箱单元库(规则/模式/样例),本地化后表不再输入 LLM。
|
|
205
|
+
"""
|
|
206
|
+
from compiler_code_units import COMPILER_UNITS
|
|
207
|
+
from python_code_units import PYTHON_UNITS
|
|
208
|
+
from graph_db_units import GRAPH_UNITS
|
|
209
|
+
from os_units import OS_UNITS
|
|
210
|
+
from browser_units import BROWSER_UNITS
|
|
211
|
+
from net_units import NET_UNITS
|
|
212
|
+
total = 0
|
|
213
|
+
for units in (COMPILER_UNITS, PYTHON_UNITS, GRAPH_UNITS,
|
|
214
|
+
OS_UNITS, BROWSER_UNITS, NET_UNITS):
|
|
215
|
+
for uid, u in units.items():
|
|
216
|
+
total += len(uid) + len(u.get("task", "")) + len(u.get("pattern", "")) \
|
|
217
|
+
+ len(str(u.get("cases", [])))
|
|
218
|
+
return total
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class VerifyCache:
|
|
222
|
+
"""校验结果本地缓存。相同指纹 → 零计算返回。
|
|
223
|
+
|
|
224
|
+
hits/misses 为进程内命中计数;「减少的 LLM 输入」以 append-only JSONL
|
|
225
|
+
审计日志持久化(data/verify_savings.jsonl,每行一次校验的输入度量)——
|
|
226
|
+
跨进程累计、可审计、可重放(复现证据)。
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
def __init__(self, path: str = CACHE_FILE, version: int = VERIFIER_VERSION,
|
|
230
|
+
savings_log: Optional[str] = SAVINGS_LOG):
|
|
231
|
+
"""组装:缓存句柄注入(默认新建;变异测试可传隔离缓存)。"""
|
|
232
|
+
self.path = path
|
|
233
|
+
self.version = version
|
|
234
|
+
self.savings_log = savings_log
|
|
235
|
+
self._data: Dict[str, Dict[str, Any]] = {}
|
|
236
|
+
self.hits = 0
|
|
237
|
+
self.misses = 0
|
|
238
|
+
self._table_chars: Optional[int] = None
|
|
239
|
+
self._load()
|
|
240
|
+
|
|
241
|
+
def _load(self) -> None:
|
|
242
|
+
"""从磁盘回放缓存快照(版本不符整体作废防脏读)。"""
|
|
243
|
+
try:
|
|
244
|
+
if os.path.exists(self.path):
|
|
245
|
+
with open(self.path, "r", encoding="utf-8") as f:
|
|
246
|
+
self._data = json.load(f)
|
|
247
|
+
except Exception:
|
|
248
|
+
self._data = {}
|
|
249
|
+
# 版本不符 → 规则已变,旧缓存结果不可信,清空重建
|
|
250
|
+
if self._data.get(_CACHE_VERSION_KEY) != self.version:
|
|
251
|
+
self._data = {_CACHE_VERSION_KEY: self.version}
|
|
252
|
+
self._flush()
|
|
253
|
+
|
|
254
|
+
def record_input(self, payload_chars: int, hit: bool) -> None:
|
|
255
|
+
"""记录一次经本地校验的输入度量(append-only 审计日志)。
|
|
256
|
+
|
|
257
|
+
每次校验(含缓存命中)都意味着:该内容(表 + 请求)不再输入 LLM →
|
|
258
|
+
省下的 token 计入日志。hit 标记是否为缓存命中(重复校验零计算)。
|
|
259
|
+
savings_log=None(隔离模式,如变异测试)时不写日志。
|
|
260
|
+
"""
|
|
261
|
+
if not self.savings_log:
|
|
262
|
+
return
|
|
263
|
+
table = self._table_chars
|
|
264
|
+
if table is None:
|
|
265
|
+
try:
|
|
266
|
+
table = _table_payload_chars()
|
|
267
|
+
except Exception:
|
|
268
|
+
table = 0
|
|
269
|
+
self._table_chars = table
|
|
270
|
+
text = payload_chars + table
|
|
271
|
+
half = text // 2
|
|
272
|
+
tokens = estimate_tokens("中" * half + "x" * (text - half))
|
|
273
|
+
try:
|
|
274
|
+
os.makedirs(DATA_DIR, exist_ok=True)
|
|
275
|
+
with open(self.savings_log, "a", encoding="utf-8") as f:
|
|
276
|
+
f.write(json.dumps({
|
|
277
|
+
"t": time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
278
|
+
"payload_chars": payload_chars, "table_chars": table,
|
|
279
|
+
"saved_chars": text, "saved_tokens": tokens,
|
|
280
|
+
"hit": bool(hit),
|
|
281
|
+
}, ensure_ascii=False) + "\n")
|
|
282
|
+
except Exception:
|
|
283
|
+
pass
|
|
284
|
+
|
|
285
|
+
def savings(self) -> Dict[str, Any]:
|
|
286
|
+
"""跨进程累计:从审计日志聚合「减少的 LLM 输入」。
|
|
287
|
+
|
|
288
|
+
读 self.savings_log(与写入同源)——此前误读模块常量 SAVINGS_LOG,
|
|
289
|
+
自定义 savings_log 路径时写入 A 聚合读 B,聚合恒 0(issue #4 审计发现)。
|
|
290
|
+
"""
|
|
291
|
+
total_chars = total_tokens = hits = reqs = 0
|
|
292
|
+
log = self.savings_log
|
|
293
|
+
try:
|
|
294
|
+
if log and os.path.exists(log):
|
|
295
|
+
for line in open(log, encoding="utf-8"):
|
|
296
|
+
line = line.strip()
|
|
297
|
+
if not line:
|
|
298
|
+
continue
|
|
299
|
+
d = json.loads(line)
|
|
300
|
+
total_chars += d.get("saved_chars", 0)
|
|
301
|
+
total_tokens += d.get("saved_tokens", 0)
|
|
302
|
+
reqs += 1
|
|
303
|
+
if d.get("hit"):
|
|
304
|
+
hits += 1
|
|
305
|
+
except Exception:
|
|
306
|
+
pass
|
|
307
|
+
return {"requests": reqs, "hits": hits,
|
|
308
|
+
"saved_chars": total_chars, "saved_tokens": total_tokens}
|
|
309
|
+
|
|
310
|
+
def get(self, fingerprint: str) -> Optional[VerifyResult]:
|
|
311
|
+
"""按指纹取缓存校验结果:命中计 hits 并标记 cached=True;未命中计 misses 返回 None。"""
|
|
312
|
+
entry = self._data.get(fingerprint)
|
|
313
|
+
if entry is None:
|
|
314
|
+
self.misses += 1
|
|
315
|
+
return None
|
|
316
|
+
self.hits += 1
|
|
317
|
+
r = VerifyResult(ok=entry["ok"], fingerprint=fingerprint, cached=True)
|
|
318
|
+
r.checks = entry.get("checks", [])
|
|
319
|
+
r.reason = entry.get("reason", "")
|
|
320
|
+
r.verified_at = entry.get("verified_at", "")
|
|
321
|
+
r.version = entry.get("version", 0)
|
|
322
|
+
return r
|
|
323
|
+
|
|
324
|
+
def put(self, result: VerifyResult) -> None:
|
|
325
|
+
"""写入指纹→结果映射并落盘持久化(附版本键防跨版本脏读)。"""
|
|
326
|
+
self._data[result.fingerprint] = {
|
|
327
|
+
"ok": result.ok,
|
|
328
|
+
"checks": result.checks,
|
|
329
|
+
"reason": result.reason,
|
|
330
|
+
"verified_at": result.verified_at,
|
|
331
|
+
"version": result.version,
|
|
332
|
+
}
|
|
333
|
+
self._data[_CACHE_VERSION_KEY] = self.version
|
|
334
|
+
self._flush()
|
|
335
|
+
|
|
336
|
+
def _flush(self) -> None:
|
|
337
|
+
"""内存态刷盘:读-合并-原子替换(多进程并发写友好)。
|
|
338
|
+
|
|
339
|
+
bootstrap 循环进程与交互进程共用同一缓存文件——先吸收磁盘上
|
|
340
|
+
他进程新增的条目再写,消除「整文件写覆盖」;临时文件+os.replace
|
|
341
|
+
原子替换,消除「读到半写状态」。残余窗口仅剩同指纹同时写的
|
|
342
|
+
几毫秒,缓存可重建(最坏=丢条目重跑 verify),工程可接受。
|
|
343
|
+
"""
|
|
344
|
+
try:
|
|
345
|
+
os.makedirs(DATA_DIR, exist_ok=True)
|
|
346
|
+
try:
|
|
347
|
+
if os.path.exists(self.path):
|
|
348
|
+
with open(self.path, "r", encoding="utf-8") as f:
|
|
349
|
+
disk = json.load(f)
|
|
350
|
+
# 版本一致性守卫:磁盘条目版本与本实例不同 → 全部忽略
|
|
351
|
+
# (版本 bump 即整体作废,不能被合并逻辑复活旧条目)
|
|
352
|
+
if isinstance(disk, dict) and disk.get(_CACHE_VERSION_KEY) == self.version:
|
|
353
|
+
for k, v in disk.items():
|
|
354
|
+
if k not in self._data:
|
|
355
|
+
self._data[k] = v
|
|
356
|
+
except Exception:
|
|
357
|
+
pass # 磁盘半写/损坏 → 以内存为准
|
|
358
|
+
tmp = self.path + ".tmp"
|
|
359
|
+
with open(tmp, "w", encoding="utf-8") as f:
|
|
360
|
+
json.dump(self._data, f, ensure_ascii=False, indent=1)
|
|
361
|
+
os.replace(tmp, self.path)
|
|
362
|
+
except Exception:
|
|
363
|
+
pass
|
|
364
|
+
|
|
365
|
+
def stats(self) -> Dict[str, Any]:
|
|
366
|
+
"""聚合统计:条目总数、通过数、命中率(hits/(hits+misses))。"""
|
|
367
|
+
entries = {k: v for k, v in self._data.items() if k != _CACHE_VERSION_KEY}
|
|
368
|
+
n = len(entries)
|
|
369
|
+
n_pass = sum(1 for v in entries.values() if v.get("ok"))
|
|
370
|
+
total_reqs = self.hits + self.misses
|
|
371
|
+
hit_rate = round(100.0 * self.hits / total_reqs, 1) if total_reqs else 0.0
|
|
372
|
+
return {"total": n, "passed": n_pass, "failed": n - n_pass,
|
|
373
|
+
"hits": self.hits, "misses": self.misses,
|
|
374
|
+
"hit_rate": hit_rate}
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
# ============ 三、校验器 ============
|
|
378
|
+
|
|
379
|
+
#: 唯一真源(codeindex._body_comments)的解析缓存:None=未解析,False=不可用
|
|
380
|
+
_CI_BODY = None
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _ci_body_comments():
|
|
384
|
+
"""解析并缓存 `codeindex._body_comments`(body 窗口的唯一真源)。
|
|
385
|
+
|
|
386
|
+
三种加载形态各试一次;全部失败返回 None(调用方走等价兜底)。
|
|
387
|
+
缓存避免每次校验都付一次 import 代价(暖缓存路径要求零额外开销)。
|
|
388
|
+
"""
|
|
389
|
+
global _CI_BODY
|
|
390
|
+
if _CI_BODY is None:
|
|
391
|
+
fn = False
|
|
392
|
+
cands = ["md_cg.codeindex"]
|
|
393
|
+
pkg = __package__ or ""
|
|
394
|
+
if pkg.count(".") >= 2: # 如 md_cg.whitebox_kb.wisdom
|
|
395
|
+
cands.insert(0, pkg.rsplit(".", 1)[0].rsplit(".", 1)[0] + ".codeindex")
|
|
396
|
+
for mod in cands:
|
|
397
|
+
try:
|
|
398
|
+
fn = getattr(importlib.import_module(mod), "_body_comments", False)
|
|
399
|
+
except Exception: # noqa: BLE001
|
|
400
|
+
continue
|
|
401
|
+
if fn:
|
|
402
|
+
break
|
|
403
|
+
_CI_BODY = fn or False
|
|
404
|
+
return _CI_BODY or None
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
class Verifier:
|
|
408
|
+
"""本地校验器:六层校验链,零 LLM。"""
|
|
409
|
+
|
|
410
|
+
def __init__(self, cache: Optional[VerifyCache] = None):
|
|
411
|
+
"""组装:缓存句柄注入(默认新建;变异测试可传隔离缓存)。"""
|
|
412
|
+
self.cache = cache or VerifyCache()
|
|
413
|
+
|
|
414
|
+
# ---------- 主入口 ----------
|
|
415
|
+
def verify(self, req: VerifyRequest) -> VerifyResult:
|
|
416
|
+
"""六层校验入口:深拷贝请求→算指纹→命中直返;未命中逐层校验并记录 token 节省度量。
|
|
417
|
+
|
|
418
|
+
(深拷贝防调用方原地修改污染指纹空间——见 P0 修复记录。)"""
|
|
419
|
+
# 不可变快照:verify 内部运行样例可能原地修改输入(如排序
|
|
420
|
+
# arr[j], arr[j+1] = ...),必须深拷贝防止污染原始 req 对象
|
|
421
|
+
# (否则同一 req 第二次校验指纹变化,缓存失效)
|
|
422
|
+
req = copy.deepcopy(req)
|
|
423
|
+
fp = req.fingerprint()
|
|
424
|
+
|
|
425
|
+
# ① 缓存命中 → 直接返回
|
|
426
|
+
cached = self.cache.get(fp)
|
|
427
|
+
|
|
428
|
+
# 输入度量:经本地校验的请求内容量(本应输入 LLM 的「表 + 请求」——
|
|
429
|
+
# 无论命中与否,走本地 = 不再输入 LLM = 省下的 token;append 审计日志)
|
|
430
|
+
try:
|
|
431
|
+
self.cache.record_input(len(req.task) + len(req.code)
|
|
432
|
+
+ len(str(req.cases)) + len(req.unit_id),
|
|
433
|
+
hit=(cached is not None))
|
|
434
|
+
except Exception:
|
|
435
|
+
pass
|
|
436
|
+
|
|
437
|
+
if cached is not None:
|
|
438
|
+
return cached
|
|
439
|
+
|
|
440
|
+
checks: List[Dict[str, Any]] = []
|
|
441
|
+
|
|
442
|
+
# ② L1 语法
|
|
443
|
+
checks.append(self._check_l1_syntax(req))
|
|
444
|
+
|
|
445
|
+
# 注入型单元(expected_structure.inject,如编译-类型检查/图遍历-BFS):
|
|
446
|
+
# 函数依赖外部注入(Graph/infer_fn/组装单元),单独运行 L2/L3 无意义
|
|
447
|
+
# (与 code_compose verify_code 的 needs_inject 语义一致——由集成测试覆盖)
|
|
448
|
+
injected = req.expected_structure.get("inject")
|
|
449
|
+
|
|
450
|
+
# ③ L2 样例(语法通过才运行;注入型跳过)
|
|
451
|
+
if checks[-1]["ok"] and not injected:
|
|
452
|
+
checks.append(self._check_l2_samples(req))
|
|
453
|
+
|
|
454
|
+
# ④ L3 边界(注入型跳过)
|
|
455
|
+
if checks[-1]["ok"] and not injected:
|
|
456
|
+
checks.append(self._check_l3_boundary(req))
|
|
457
|
+
|
|
458
|
+
# ⑤ 规范符合性
|
|
459
|
+
checks.append(self._check_spec_compliance(req))
|
|
460
|
+
|
|
461
|
+
# ⑥ 集成测试(有 deps 才做)
|
|
462
|
+
if req.deps:
|
|
463
|
+
checks.append(self._check_integration(req))
|
|
464
|
+
|
|
465
|
+
ok = all(c["ok"] for c in checks)
|
|
466
|
+
reason = ""
|
|
467
|
+
if not ok:
|
|
468
|
+
failed = [c for c in checks if not c["ok"]]
|
|
469
|
+
reason = failed[0]["evidence"] if failed else "未知失败"
|
|
470
|
+
|
|
471
|
+
result = VerifyResult(ok=ok, checks=checks, fingerprint=fp, reason=reason,
|
|
472
|
+
verified_at=time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
473
|
+
version=VERIFIER_VERSION)
|
|
474
|
+
# 写缓存
|
|
475
|
+
self.cache.put(result)
|
|
476
|
+
return result
|
|
477
|
+
|
|
478
|
+
# ---------- L1 语法 ----------
|
|
479
|
+
def _check_l1_syntax(self, req: VerifyRequest) -> Dict[str, Any]:
|
|
480
|
+
"""ast.parse + 结构规则(函数定义/占位残留/括号平衡)。"""
|
|
481
|
+
code = req.code
|
|
482
|
+
# 语法解析
|
|
483
|
+
try:
|
|
484
|
+
tree = ast.parse(code)
|
|
485
|
+
except SyntaxError as e:
|
|
486
|
+
return {"level": "L1语法", "ok": False,
|
|
487
|
+
"evidence": f"语法错误: {e}"}
|
|
488
|
+
|
|
489
|
+
# 结构规则
|
|
490
|
+
errors = []
|
|
491
|
+
funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
|
|
492
|
+
if not funcs:
|
|
493
|
+
errors.append("无函数定义")
|
|
494
|
+
# 占位残留({fn} 等模板占位符未替换)
|
|
495
|
+
if "{fn}" in code or "{param" in code:
|
|
496
|
+
errors.append("存在未替换的模板占位符")
|
|
497
|
+
# 括号平衡:ast.parse 已通过即证明语法合法(字符串/注释内括号
|
|
498
|
+
# 不应计入——字符计数会误判,如 pattern 注释含裸括号)
|
|
499
|
+
# 禁止裸 import(白箱代码应自包含)——但允许标准库
|
|
500
|
+
imports = [n for n in ast.walk(tree) if isinstance(n, (ast.Import, ast.ImportFrom))]
|
|
501
|
+
STDLIB_OK = {"collections", "typing", "functools", "itertools", "math",
|
|
502
|
+
"random", "json", "re", "string", "dataclasses", "abc",
|
|
503
|
+
"os", "sys", "io", "struct", "asyncio", "heapq", "queue",
|
|
504
|
+
"threading", "time", "socket", "hashlib", "uuid", "base64",
|
|
505
|
+
"statistics", "bisect", "decimal", "fractions"}
|
|
506
|
+
if imports and not req.expected_structure.get("allow_import"):
|
|
507
|
+
bad = []
|
|
508
|
+
for n in imports:
|
|
509
|
+
if isinstance(n, ast.ImportFrom):
|
|
510
|
+
top = (n.module or "").split(".")[0]
|
|
511
|
+
if top and top not in STDLIB_OK:
|
|
512
|
+
bad.append(n.module)
|
|
513
|
+
else:
|
|
514
|
+
for alias in n.names:
|
|
515
|
+
top = alias.name.split(".")[0]
|
|
516
|
+
if top not in STDLIB_OK:
|
|
517
|
+
bad.append(alias.name)
|
|
518
|
+
if bad:
|
|
519
|
+
return {"level": "L1语法", "ok": False,
|
|
520
|
+
"evidence": f"存在非标准库导入: {bad}"}
|
|
521
|
+
|
|
522
|
+
if errors:
|
|
523
|
+
return {"level": "L1语法", "ok": False, "evidence": "; ".join(errors)}
|
|
524
|
+
return {"level": "L1语法", "ok": True,
|
|
525
|
+
"evidence": f"语法通过({len(funcs)} 个函数定义)"}
|
|
526
|
+
|
|
527
|
+
# ---------- L2 样例 ----------
|
|
528
|
+
def _check_l2_samples(self, req: VerifyRequest) -> Dict[str, Any]:
|
|
529
|
+
"""每个 (输入, 期望) 运行断言。"""
|
|
530
|
+
if not req.cases:
|
|
531
|
+
return {"level": "L2样例", "ok": True, "evidence": "无样例(跳过)"}
|
|
532
|
+
|
|
533
|
+
ns: Dict[str, Any] = {}
|
|
534
|
+
try:
|
|
535
|
+
exec(compile(req.code, "<verify>", "exec"), ns)
|
|
536
|
+
except Exception as e:
|
|
537
|
+
return {"level": "L2样例", "ok": False,
|
|
538
|
+
"evidence": f"代码编译/执行失败: {e}"}
|
|
539
|
+
|
|
540
|
+
# 找被测函数(第一个函数定义)
|
|
541
|
+
func_name = None
|
|
542
|
+
try:
|
|
543
|
+
tree = ast.parse(req.code)
|
|
544
|
+
funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
|
|
545
|
+
func_name = funcs[0].name if funcs else None
|
|
546
|
+
except Exception:
|
|
547
|
+
pass
|
|
548
|
+
|
|
549
|
+
if not func_name or func_name not in ns:
|
|
550
|
+
return {"level": "L2样例", "ok": False,
|
|
551
|
+
"evidence": f"找不到被测函数(期望 {func_name})"}
|
|
552
|
+
|
|
553
|
+
fn = ns[func_name]
|
|
554
|
+
ran = 0
|
|
555
|
+
for case_idx, (inp, exp) in enumerate(req.cases, 1):
|
|
556
|
+
if inp == "call":
|
|
557
|
+
# 'call' 特殊标记:域注入型单元(L2 由域集成测试覆盖,
|
|
558
|
+
# 与 code_compose verify_code 语义一致——不把 'call' 当输入)
|
|
559
|
+
continue
|
|
560
|
+
ran += 1
|
|
561
|
+
try:
|
|
562
|
+
got = fn(inp) if not isinstance(inp, tuple) else fn(*inp)
|
|
563
|
+
except Exception as e:
|
|
564
|
+
return {"level": "L2样例", "ok": False,
|
|
565
|
+
"evidence": f"样例{case_idx} 崩溃: {inp} → {e}"}
|
|
566
|
+
if not _assert_match(got, exp):
|
|
567
|
+
return {"level": "L2样例", "ok": False,
|
|
568
|
+
"evidence": f"样例{case_idx} 失败: {inp} → {got}(期望 {exp})"}
|
|
569
|
+
if ran == 0:
|
|
570
|
+
return {"level": "L2样例", "ok": True,
|
|
571
|
+
"evidence": "注入型单元(样例均为 call 标记,由集成测试覆盖)"}
|
|
572
|
+
return {"level": "L2样例", "ok": True,
|
|
573
|
+
"evidence": f"{ran} 组样例全部通过"}
|
|
574
|
+
|
|
575
|
+
# ---------- L3 边界 ----------
|
|
576
|
+
def _check_l3_boundary(self, req: VerifyRequest) -> Dict[str, Any]:
|
|
577
|
+
"""额外边界用例:None/空/极端值(仅单参数函数——多参数函数
|
|
578
|
+
边界形态依赖参数语义,统一生成会误判,由单元自带样例覆盖)。"""
|
|
579
|
+
extra_cases = self._gen_boundary_cases(req.task)
|
|
580
|
+
if not extra_cases:
|
|
581
|
+
return {"level": "L3边界", "ok": True, "evidence": "无额外边界用例"}
|
|
582
|
+
|
|
583
|
+
ns: Dict[str, Any] = {}
|
|
584
|
+
try:
|
|
585
|
+
exec(compile(req.code, "<verify>", "exec"), ns)
|
|
586
|
+
except Exception as e:
|
|
587
|
+
return {"level": "L3边界", "ok": False, "evidence": f"执行失败: {e}"}
|
|
588
|
+
|
|
589
|
+
func_name = None
|
|
590
|
+
n_args = None
|
|
591
|
+
try:
|
|
592
|
+
tree = ast.parse(req.code)
|
|
593
|
+
funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
|
|
594
|
+
if funcs:
|
|
595
|
+
func_name = funcs[0].name
|
|
596
|
+
args = funcs[0].args
|
|
597
|
+
n_args = len(args.posonlyargs) + len(args.args)
|
|
598
|
+
except Exception:
|
|
599
|
+
pass
|
|
600
|
+
|
|
601
|
+
if not func_name or func_name not in ns:
|
|
602
|
+
return {"level": "L3边界", "ok": True, "evidence": "跳过(无函数)"}
|
|
603
|
+
|
|
604
|
+
if n_args is not None and n_args != 1:
|
|
605
|
+
return {"level": "L3边界", "ok": True,
|
|
606
|
+
"evidence": f"跳过(函数 {n_args} 个参数,边界形态依赖参数语义)"}
|
|
607
|
+
|
|
608
|
+
# 单参数但参数是图/状态/字典类(adj/graph/table/state…)→ 边界形态
|
|
609
|
+
# 依赖具体语义(如 [] 传 max_clique(adj) 是类型错误而非代码缺陷)——
|
|
610
|
+
# 由单元自带样例覆盖,不统一生成边界
|
|
611
|
+
DICTISH_ARGS = {"adj", "graph", "g", "table", "state", "env", "cond",
|
|
612
|
+
"nodes", "node", "map", "units", "db", "queue",
|
|
613
|
+
"buffer", "registry", "cert"}
|
|
614
|
+
if n_args == 1 and funcs[0].args.args \
|
|
615
|
+
and funcs[0].args.args[0].arg in DICTISH_ARGS:
|
|
616
|
+
return {"level": "L3边界", "ok": True,
|
|
617
|
+
"evidence": f"跳过(参数 {funcs[0].args.args[0].arg} 为图/状态类,边界由样例覆盖)"}
|
|
618
|
+
|
|
619
|
+
fn = ns[func_name]
|
|
620
|
+
for case_idx, (inp, exp) in enumerate(extra_cases, 1):
|
|
621
|
+
try:
|
|
622
|
+
got = fn(inp) if not isinstance(inp, tuple) else fn(*inp)
|
|
623
|
+
except Exception as e:
|
|
624
|
+
# 边界用例允许异常返回(如空列表→None/报错均可接受)
|
|
625
|
+
if exp == "any":
|
|
626
|
+
continue
|
|
627
|
+
return {"level": "L3边界", "ok": False,
|
|
628
|
+
"evidence": f"边界用例{case_idx} 崩溃: {inp} → {e}"}
|
|
629
|
+
if exp != "any" and not _assert_match(got, exp):
|
|
630
|
+
return {"level": "L3边界", "ok": False,
|
|
631
|
+
"evidence": f"边界用例{case_idx} 失败: {inp} → {got}(期望 {exp})"}
|
|
632
|
+
return {"level": "L3边界", "ok": True,
|
|
633
|
+
"evidence": f"{len(extra_cases)} 组边界用例通过"}
|
|
634
|
+
|
|
635
|
+
def _gen_boundary_cases(self, task: str) -> List[Tuple[Any, Any]]:
|
|
636
|
+
"""根据任务类型生成边界用例(白箱规则,非 LLM)。
|
|
637
|
+
|
|
638
|
+
注意:不带「反转」——字典反转等单元收 dict(值变键),序列边界
|
|
639
|
+
[] 是类型错误而非代码缺陷;反转边界由单元自带样例覆盖。
|
|
640
|
+
"""
|
|
641
|
+
t = task.lower()
|
|
642
|
+
if any(w in t for w in ("排序", "sort", "去重", "dedup")):
|
|
643
|
+
return [([], []), ([1], [1]), (None, "any")]
|
|
644
|
+
if any(w in t for w in ("最大", "max", "最小", "min")):
|
|
645
|
+
return [([], None), ([5], 5), (None, "any")] # 空列表→None(与单元语义一致)
|
|
646
|
+
if any(w in t for w in ("求和", "sum")):
|
|
647
|
+
return [([], 0), (None, "any")]
|
|
648
|
+
if any(w in t for w in ("计数", "count", "频率", "freq")):
|
|
649
|
+
return [([], {}), (None, "any")] # Counter 语义:空列表 → {}
|
|
650
|
+
# 字符串族:拆分/替换/拼接/判断/对齐/哈希/分词/格式化——空串语义因函数而异
|
|
651
|
+
# (分词空串→[]、拼接空列表→''),统一断言会误判 → 只查「空串处理不崩溃」
|
|
652
|
+
if any(w in t for w in ("字符串", "拆分", "替换", "拼接", "判断", "对齐",
|
|
653
|
+
"哈希", "分词", "格式化")):
|
|
654
|
+
return [("", "any")]
|
|
655
|
+
# 数学族:舍入/统计/数学函数/均值/众数/分位数——零输入形态因函数而异
|
|
656
|
+
# (列表统计收 []、数值函数收 0),统一断言会误判 → 只查「零输入不崩溃」
|
|
657
|
+
if any(w in t for w in ("舍入", "统计", "数学函数", "均值", "众数", "分位数")):
|
|
658
|
+
return [(0, "any")]
|
|
659
|
+
return []
|
|
660
|
+
|
|
661
|
+
# ---------- ⑤ 规范符合性(白箱规则 + condition_kb) ----------
|
|
662
|
+
def _check_spec_compliance(self, req: VerifyRequest) -> Dict[str, Any]:
|
|
663
|
+
"""结构规范 + 语义对齐(condition_kb 查条件路由表)。"""
|
|
664
|
+
errors = []
|
|
665
|
+
|
|
666
|
+
# 结构规范:任务关键词 → 应包含的结构(白箱规则)
|
|
667
|
+
# 注意:只保留「强信号」结构(排序/去重/反转有字面可查的典型结构)。
|
|
668
|
+
# 「循环」类任务在编译器域以字节码/展开形式实现(无 for/while 字面量),
|
|
669
|
+
# 统一强查会误判——循环语义由 L2 样例裁决,不在此字面强查。
|
|
670
|
+
# 排序:接受比较符、sorted(key=) 键控、reorder 重排(排序键控/报文重排序)
|
|
671
|
+
# 反转:排除「字典反转」(值变键语义,无序列反转结构)
|
|
672
|
+
structure_rules = {
|
|
673
|
+
"排序": ["<", ">", "sorted", "key=", "reorder"],
|
|
674
|
+
"sort": ["<", ">", "sorted", "key=", "reorder"],
|
|
675
|
+
"去重": ["set", "seen"], "dedup": ["set", "seen"],
|
|
676
|
+
"反转": ["::-1", "reverse", "reversed"],
|
|
677
|
+
}
|
|
678
|
+
for kw, patterns in structure_rules.items():
|
|
679
|
+
if kw in req.task and req.expected_structure.get("skip_structure"):
|
|
680
|
+
continue
|
|
681
|
+
if kw in req.task:
|
|
682
|
+
if kw in ("反转",) and "字典" in req.task:
|
|
683
|
+
continue # 字典反转:值变键语义,无序列反转结构
|
|
684
|
+
# 大小写不敏感:本表是**语言无关的任务语义判据**(要求「去重结构
|
|
685
|
+
# 出现」这一事实),而字面取自 Python 命名习惯(set/seen)——
|
|
686
|
+
# JS 标准 API 写作 `new Set(arr)`,敏感匹配把正确实现判为
|
|
687
|
+
# 「未见对应结构」(实测 code-js/去重 假失败,v10 修正)。
|
|
688
|
+
# 放宽仅限大小写,令牌本身仍必须出现(不弱化判据强度)。
|
|
689
|
+
hit = any(p.lower() in req.code.lower() for p in patterns)
|
|
690
|
+
if not hit and not req.expected_structure.get("structure_optional"):
|
|
691
|
+
errors.append(f"任务含「{kw}」但代码未见对应结构 {patterns}")
|
|
692
|
+
|
|
693
|
+
# 语义对齐:condition_kb 查该任务的条件规律,代码应有对应处理
|
|
694
|
+
try:
|
|
695
|
+
from condition_kb import ConditionKB
|
|
696
|
+
kb = ConditionKB()
|
|
697
|
+
sem = kb.lookup(req.task)
|
|
698
|
+
if sem and sem.get("match"):
|
|
699
|
+
# 查到的规律描述包含条件词 → 代码应体现条件分支
|
|
700
|
+
cond_words = ["如果", "若", "when", "if", "条件", "情况"]
|
|
701
|
+
has_cond = any(w in req.code for w in cond_words)
|
|
702
|
+
if any(w in sem.get("match", "") for w in ["条件", "如果", "当"]) \
|
|
703
|
+
and not has_cond \
|
|
704
|
+
and not req.expected_structure.get("no_cond_needed"):
|
|
705
|
+
errors.append(f"condition_kb 提示「{sem.get('match', '')[:30]}」需条件处理但代码无分支")
|
|
706
|
+
except Exception:
|
|
707
|
+
pass # condition_kb 不可用时跳过(不影响主校验)
|
|
708
|
+
|
|
709
|
+
# 条件论注释规范(WB-SPEC v1.2,用户条件论要求):
|
|
710
|
+
# R1 函数/类等抽象的总体功能必须中文注释(docstring 或紧跟 # 中文注释)
|
|
711
|
+
# R2 注释须为中文语境(英文缩写嵌入中文说明;纯英文注释行违规)
|
|
712
|
+
# R3/R4(六要素契约)恒执行并回报达标率——见 _check_comment_spec 的纪律说明
|
|
713
|
+
self._comment_notes = []
|
|
714
|
+
self._ccg_stat = {}
|
|
715
|
+
errors.extend(self._check_comment_spec(req))
|
|
716
|
+
|
|
717
|
+
if errors:
|
|
718
|
+
return {"level": "规范符合性", "ok": False, "ccg": self._ccg_stat,
|
|
719
|
+
"evidence": "; ".join(errors)}
|
|
720
|
+
# evidence 必须**如实**:改造前无论 R3/R4 是否执行都写「条件论注释通过」,
|
|
721
|
+
# 等于把「没检查」冒充「检查通过」(固化记录里留下了这类伪证)。
|
|
722
|
+
detail = ";".join(self._comment_notes) or "R3/R4 无可检符号"
|
|
723
|
+
return {"level": "规范符合性", "ok": True, "ccg": self._ccg_stat,
|
|
724
|
+
"evidence": f"结构规范 + 语义对齐 + 条件论注释 R1/R2 通过({detail})"}
|
|
725
|
+
|
|
726
|
+
def _check_comment_spec(self, req: VerifyRequest) -> list:
|
|
727
|
+
"""条件论注释规范(WB-SPEC v1.2):R1 函数/类中文注释 + R2 注释中文语境。
|
|
728
|
+
|
|
729
|
+
R1:每个 FunctionDef/AsyncFunctionDef/ClassDef 必须有 docstring 或
|
|
730
|
+
紧跟的 # 中文注释描述其总体功能。
|
|
731
|
+
R2:注释行必须为中文语境(含中文字符)——英文缩写嵌入中文说明;
|
|
732
|
+
纯英文注释行(无任何中文)违规。
|
|
733
|
+
非 Python 源码分两档(见下方 except 分支):Python 模板(含 `{fn}` 占位符)
|
|
734
|
+
展开后走本函数的**全量**判据;真·非 Python(Rust/JS)走文本判据。
|
|
735
|
+
expected_structure.no_comment_spec=True 可豁免(如注入型组装片段)。
|
|
736
|
+
"""
|
|
737
|
+
if req.expected_structure.get("no_comment_spec"):
|
|
738
|
+
return []
|
|
739
|
+
code = req.code
|
|
740
|
+
try:
|
|
741
|
+
tree = ast.parse(code)
|
|
742
|
+
except SyntaxError:
|
|
743
|
+
# 分支 A·Python 模板:`*_UNITS` 的 pattern 是**带占位符的模板**
|
|
744
|
+
# (`def {fn}(arr):`),ast.parse 必然 SyntaxError。改造前这类模板
|
|
745
|
+
# 与 Rust/JS 一样落到 `return []` 静默跳过;v8 起落到文本判据——
|
|
746
|
+
# 但那是**降级判据**(只查三要素字面,不查 R1 逐符号中文注释 / R2
|
|
747
|
+
# 纯英文注释行)。故此优先展开占位符再进 Python 判据(v9),
|
|
748
|
+
# 使 Python 模板与六域单元获得同强度判据。
|
|
749
|
+
rendered = self._render_placeholders(code, req)
|
|
750
|
+
if rendered != code:
|
|
751
|
+
try:
|
|
752
|
+
tree = ast.parse(rendered)
|
|
753
|
+
except SyntaxError:
|
|
754
|
+
return self._check_comment_spec_text(req)
|
|
755
|
+
code = rendered
|
|
756
|
+
else:
|
|
757
|
+
# 分支 B·真非 Python 源码(Rust / JavaScript 模板):ast 无法解析。
|
|
758
|
+
# 改造前此处 `return []` = **静默跳过**,等于给多语言单元发
|
|
759
|
+
# 「假通过」——实测 RUST_UNITS/JS_UNITS 各 5 单元的 CCG 契约 0/5
|
|
760
|
+
# 达标,而 verifier 报「R3 缺 0」(`_ccg_stat` 留空、不进分母)
|
|
761
|
+
# → 审计报告完全隐身。
|
|
762
|
+
return self._check_comment_spec_text(req)
|
|
763
|
+
src_lines = code.splitlines()
|
|
764
|
+
_has_cn = lambda s: any('\u4e00' <= c <= '\u9fff' for c in s)
|
|
765
|
+
errs = []
|
|
766
|
+
# R1:函数/类中文注释
|
|
767
|
+
missing = []
|
|
768
|
+
for f in ast.walk(tree):
|
|
769
|
+
if not isinstance(f, (ast.FunctionDef, ast.AsyncFunctionDef,
|
|
770
|
+
ast.ClassDef)):
|
|
771
|
+
continue
|
|
772
|
+
doc = ast.get_docstring(f)
|
|
773
|
+
if doc and _has_cn(doc):
|
|
774
|
+
continue
|
|
775
|
+
ok = False
|
|
776
|
+
# 查 def/class 前后各 2 行内的 # 中文注释(docstring 已单独检查)
|
|
777
|
+
for ln in src_lines[max(0, f.lineno - 2):f.lineno + 3]:
|
|
778
|
+
if ln.strip().startswith('#') and _has_cn(ln):
|
|
779
|
+
ok = True
|
|
780
|
+
break
|
|
781
|
+
if not ok:
|
|
782
|
+
missing.append(f.name)
|
|
783
|
+
if missing:
|
|
784
|
+
errs.append(f"函数/类缺中文注释(条件论 R1): {missing[:5]}")
|
|
785
|
+
# R2:注释行中文语境
|
|
786
|
+
bad_lines = []
|
|
787
|
+
for i, ln in enumerate(src_lines, 1):
|
|
788
|
+
s = ln.strip()
|
|
789
|
+
if s.startswith('#') and not _has_cn(s) and len(s) > 3:
|
|
790
|
+
bad_lines.append(f"L{i}")
|
|
791
|
+
if bad_lines:
|
|
792
|
+
errs.append(f"纯英文注释行(条件论 R2,须中文说明): {bad_lines[:5]}")
|
|
793
|
+
# R3/R4:CCG 六要素注释——**默认软模式恒执行**(2026-09-17 修复)。
|
|
794
|
+
#
|
|
795
|
+
# 改造前的缺陷(「规范没有被执行」的机械根因,用户 2026-09-17 定性):
|
|
796
|
+
# ① R3/R4 被包在 `require_cond_comment` / `require_not_cond` 开关内,
|
|
797
|
+
# 而全仓**无任何调用方**传入这两个键 → 判据为**死代码**,恒不执行;
|
|
798
|
+
# ② 但 evidence 恒写「条件论注释通过」——R1/R2 通过即谎报六要素达标,
|
|
799
|
+
# 于是 code_solidified.json 留下「零标记却通过」的**伪证**;
|
|
800
|
+
# ③ 判据自身只查**首个** def、窗口只覆盖 def 前 2 行——与既有单元库
|
|
801
|
+
# 104+ 处标记的实际形态(`# 生效条件:` 紧跟 def 行之后)物理不相容。
|
|
802
|
+
# 现纪律:软模式**恒执行**并把达标率写入 evidence(诚实报告,不静默),
|
|
803
|
+
# 硬拦截仅在开关被显式传入时(全库升级完成后由调用方开启)。
|
|
804
|
+
r3_missing, total = self._cond_comment_missing(tree, src_lines)
|
|
805
|
+
if total:
|
|
806
|
+
self._comment_notes.append(
|
|
807
|
+
f"R3 三要素 {total - len(r3_missing)}/{total} 达标")
|
|
808
|
+
r4_missing, _ = self._not_cond_missing(tree, src_lines)
|
|
809
|
+
# 结构化留痕:软模式不拦截,若只把结论写进 evidence **字符串**,
|
|
810
|
+
# `--audit` 就无法机械汇总缺失清单——「规范没被执行」正是这样隐身的。
|
|
811
|
+
self._ccg_stat = {"r3_total": total, "r3_missing": r3_missing,
|
|
812
|
+
"r4_missing": r4_missing}
|
|
813
|
+
if r3_missing:
|
|
814
|
+
msg = f"条件论三要素注释缺失(R3): {r3_missing[:5]}"
|
|
815
|
+
if req.expected_structure.get("require_cond_comment"):
|
|
816
|
+
errs.append(msg)
|
|
817
|
+
else:
|
|
818
|
+
self._comment_notes.append(msg + "(软模式,不拦截)")
|
|
819
|
+
if r4_missing:
|
|
820
|
+
msg = f"不适用条件缺失(R4·代码语义条件协议): {r4_missing[:5]}"
|
|
821
|
+
if req.expected_structure.get("require_not_cond"):
|
|
822
|
+
errs.append(msg)
|
|
823
|
+
else:
|
|
824
|
+
self._comment_notes.append(msg + "(软模式,不拦截)")
|
|
825
|
+
return errs
|
|
826
|
+
|
|
827
|
+
def _render_placeholders(self, code: str, req: VerifyRequest) -> str:
|
|
828
|
+
"""展开单元模板占位符(`{fn}` → 具体标识符),使模板可被 ast 解析。
|
|
829
|
+
|
|
830
|
+
为什么需要:`*_UNITS` 的 pattern 是模板形态(`def {fn}(arr):`),
|
|
831
|
+
未经 `code_compose.compose_code` 替换前**不是合法 Python**——直接
|
|
832
|
+
ast.parse 必 SyntaxError,判据就会降级(v7 静默跳过 / v8 文本判据)。
|
|
833
|
+
展开后 template 与「运行时真实生成的代码」同形,R1/R2/R3/R4 全量判据
|
|
834
|
+
才真正作用于模板本体(v9)。
|
|
835
|
+
|
|
836
|
+
占位符名真源:`expected_structure["params"]`(调用方传单元声明的参数名),
|
|
837
|
+
缺省回退约定名 `("fn",)`(`code_compose` 第 17 行声明的模板约定)。
|
|
838
|
+
只替换**声明过的**占位符——不做正则通配,避免误伤 f-string / dict 字面量
|
|
839
|
+
(`{x}`、`{1: 1}` 形态)。
|
|
840
|
+
返回值与输入相同 = 无占位符可展开(调用方据此判定「真·非 Python」)。
|
|
841
|
+
"""
|
|
842
|
+
names = req.expected_structure.get("params") or ("fn",)
|
|
843
|
+
out = code
|
|
844
|
+
for p in names:
|
|
845
|
+
out = out.replace("{" + str(p) + "}", "solve")
|
|
846
|
+
return out
|
|
847
|
+
|
|
848
|
+
def _check_comment_spec_text(self, req: VerifyRequest) -> list:
|
|
849
|
+
"""非 Python 源码的 CCG 契约判据(Rust / JavaScript,`//` 或 `#` 注释)。
|
|
850
|
+
|
|
851
|
+
存在理由:`ast.parse` 对非 Python 源码抛 `SyntaxError`。改造前该分支直接
|
|
852
|
+
`return []` = **静默跳过**——多语言单元在校验器眼里永远「合规」,
|
|
853
|
+
`_ccg_stat` 留空不进分母,缺口在审计报告里完全隐身
|
|
854
|
+
(实测:verifier 报 R3 缺 0,文本实缺 5/5)。
|
|
855
|
+
|
|
856
|
+
判据与 Python 分支**同口径**(分层不重复):
|
|
857
|
+
· 入口符号 = 首个非空非注释行(Rust `fn {fn}(...)`、JS `function {fn}(...)`);
|
|
858
|
+
· 注释块 = 入口行之后的连续 `//`/`#` 行;
|
|
859
|
+
· R3 三要素(生效条件/子功能/执行)+ R4 不适用条件——软模式,恒执行;
|
|
860
|
+
· R1/R2 精神:注释块须含中文(缺失则硬拦截,与 Python 分支一致)。
|
|
861
|
+
"""
|
|
862
|
+
lines = req.code.splitlines()
|
|
863
|
+
entry = next((i for i, ln in enumerate(lines)
|
|
864
|
+
if ln.strip() and not ln.strip().startswith(("//", "#"))),
|
|
865
|
+
None)
|
|
866
|
+
if entry is None:
|
|
867
|
+
return []
|
|
868
|
+
block = []
|
|
869
|
+
for ln in lines[entry + 1:]:
|
|
870
|
+
s = ln.strip()
|
|
871
|
+
if s.startswith(("//", "#")):
|
|
872
|
+
block.append(s.lstrip("/#").strip())
|
|
873
|
+
elif not s:
|
|
874
|
+
continue
|
|
875
|
+
else:
|
|
876
|
+
break
|
|
877
|
+
joined = " ".join(block)
|
|
878
|
+
_has_cn = lambda s: any('\u4e00' <= c <= '\u9fff' for c in s)
|
|
879
|
+
errs = []
|
|
880
|
+
if not block or not _has_cn(joined):
|
|
881
|
+
errs.append(f"入口符号缺中文注释(条件论 R1/R2): L{entry + 1}")
|
|
882
|
+
need = ("生效条件", "子功能", "执行")
|
|
883
|
+
r3_missing = [k for k in need if k not in joined]
|
|
884
|
+
r4_missing = [] if "不适用条件" in joined else [f"L{entry + 1}"]
|
|
885
|
+
# 与 Python 分支同款结构化留痕(分母记 1 = 入口符号,不是「整个文件」)
|
|
886
|
+
self._ccg_stat = {"r3_total": 1, "r3_missing": r3_missing,
|
|
887
|
+
"r4_missing": r4_missing, "mode": "text"}
|
|
888
|
+
if r3_missing:
|
|
889
|
+
msg = f"条件论三要素注释缺失(R3·文本判据): {r3_missing}"
|
|
890
|
+
if req.expected_structure.get("require_cond_comment"):
|
|
891
|
+
errs.append(msg)
|
|
892
|
+
else:
|
|
893
|
+
self._comment_notes.append(msg + "(软模式,不拦截)")
|
|
894
|
+
if r4_missing:
|
|
895
|
+
msg = "不适用条件缺失(R4·文本判据)"
|
|
896
|
+
if req.expected_structure.get("require_not_cond"):
|
|
897
|
+
errs.append(msg)
|
|
898
|
+
else:
|
|
899
|
+
self._comment_notes.append(msg + "(软模式,不拦截)")
|
|
900
|
+
return errs
|
|
901
|
+
|
|
902
|
+
def _ccg_scope(self, tree) -> list:
|
|
903
|
+
"""R3/R4 共同判定面:CCG 注释的**检查对象** = 单元入口(唯一口径)。
|
|
904
|
+
|
|
905
|
+
三处判据校准(2026-09-17,依据 `--audit` 实测暴露的噪声):
|
|
906
|
+
① **只用 `tree.body` 顶层**——改造前用 `ast.walk`,会把**类内方法**
|
|
907
|
+
也展开计入(实测 `Cat` 与 `Cat.speak` 同现、`speak` 重复两次);
|
|
908
|
+
② **排除 `*_test` / `test_*` / `_` 前缀**——测试样例代码与私有辅助
|
|
909
|
+
不是契约对象(实测 `closure_test`/`gen_test`/`with_test` 等被计入分母);
|
|
910
|
+
③ **取首个合格符号 = 单元对外入口**——`*_units.py` 的既有约定:
|
|
911
|
+
pattern 首符号承载「何时可调 / 做什么 / 怎么用」,内部辅助
|
|
912
|
+
(dfs/walk/find/h1/up/down)无外部调用方,由 R1 中文注释覆盖。
|
|
913
|
+
与改造前注释声明的设计意图一致(「检查首个 def/class;内部辅助函数由
|
|
914
|
+
R1 覆盖」),修的是**窗口错位与谎报**,不是检查面。
|
|
915
|
+
"""
|
|
916
|
+
for n in getattr(tree, "body", []):
|
|
917
|
+
if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
|
918
|
+
nm = n.name
|
|
919
|
+
if (nm.startswith("_") or nm.endswith("_test")
|
|
920
|
+
or nm.startswith("test_")):
|
|
921
|
+
continue
|
|
922
|
+
return [n]
|
|
923
|
+
return []
|
|
924
|
+
|
|
925
|
+
def _ccg_block(self, f, src_lines) -> tuple:
|
|
926
|
+
"""读取单个符号的 CCG 注释块:返回 (注释行文本列表, docstring)。
|
|
927
|
+
|
|
928
|
+
窗口 = def/class 签名行之后、**函数体首个语句之前**的 `#` 注释
|
|
929
|
+
(多行签名的续行非 `#` 起始,自然跳过)+ docstring。
|
|
930
|
+
该窗口即既有单元库 104+ 处标记的实际物理位置(`# 生效条件:` 紧贴
|
|
931
|
+
def 行下一行);`f.body[0].lineno` 保证多行签名下窗口仍正确。
|
|
932
|
+
|
|
933
|
+
**唯一真源**(2026-09-17 裁决 A):窗口计算委托
|
|
934
|
+
`codeindex._body_comments`。历史问题:本函数曾自述「与
|
|
935
|
+
`codeindex._body_comments` 同款语义」,而该名当时**并不存在**——
|
|
936
|
+
悬空引用即第二份真相,已由本次收敛坐实。
|
|
937
|
+
|
|
938
|
+
兜底(第 7 条):本模块有三种加载形态——`-m md_cg.whitebox_kb.wisdom.verifier`、
|
|
939
|
+
顶层 `wisdom.verifier`(wheel 发布态,见 code_compose.py)、直跑
|
|
940
|
+
`python verifier.py`;后两者 import 不到 `md_cg.codeindex`,故保留
|
|
941
|
+
**逐字等价的兜底**,等价性由 `md_cg/test_codeindex.py` 机械守卫。
|
|
942
|
+
"""
|
|
943
|
+
body = getattr(f, "body", None)
|
|
944
|
+
body_lineno = body[0].lineno if body else f.lineno
|
|
945
|
+
raw = None
|
|
946
|
+
fn = _ci_body_comments()
|
|
947
|
+
if fn is not None:
|
|
948
|
+
raw = fn(src_lines, f.lineno, body_lineno)
|
|
949
|
+
if raw is None:
|
|
950
|
+
# 兜底(第 7 条):与唯一真源**逐字等价**,等价性由
|
|
951
|
+
# md_cg/test_codeindex.py 的委托等价断言机械守卫。
|
|
952
|
+
raw = [ln.strip()
|
|
953
|
+
for ln in src_lines[f.lineno:max(f.lineno, body_lineno - 1)]
|
|
954
|
+
if ln.strip().startswith("#")]
|
|
955
|
+
out = [s.lstrip("#").strip() for s in raw]
|
|
956
|
+
doc = (ast.get_docstring(f) or "").strip()
|
|
957
|
+
return out, doc
|
|
958
|
+
|
|
959
|
+
def _cond_comment_missing(self, tree, src_lines) -> tuple:
|
|
960
|
+
"""R3:缺「生效条件/子功能/执行」三要素的符号清单;返回 (missing, total)。
|
|
961
|
+
|
|
962
|
+
要素名真源 = `nodefile.CCG_CONTRACT_ROLES`(六要素接口契约);
|
|
963
|
+
R3 只抽其中三要素——功能名由 R1 覆盖、验证方式与不适用条件由
|
|
964
|
+
`codeindex.render` 落槽(分层不重复,改造前此三要素从未被检查过)。
|
|
965
|
+
"""
|
|
966
|
+
funcs = self._ccg_scope(tree)
|
|
967
|
+
need = ("生效条件", "子功能", "执行")
|
|
968
|
+
missing = []
|
|
969
|
+
for f in funcs:
|
|
970
|
+
block, doc = self._ccg_block(f, src_lines)
|
|
971
|
+
joined = " ".join(block) + " " + doc
|
|
972
|
+
if not all(k in joined for k in need):
|
|
973
|
+
missing.append(f.name)
|
|
974
|
+
return missing, len(funcs)
|
|
975
|
+
|
|
976
|
+
def _not_cond_missing(self, tree, src_lines) -> tuple:
|
|
977
|
+
"""R4:缺「不适用条件」的符号清单;返回 (missing, total)。
|
|
978
|
+
|
|
979
|
+
不适用条件 = 拒绝域 rejection_domain(何时**不能**调用)——
|
|
980
|
+
知道边界才敢调用,与「何时能调用」同等重要。
|
|
981
|
+
"""
|
|
982
|
+
funcs = self._ccg_scope(tree)
|
|
983
|
+
missing = []
|
|
984
|
+
for f in funcs:
|
|
985
|
+
block, doc = self._ccg_block(f, src_lines)
|
|
986
|
+
joined = " ".join(block) + " " + doc
|
|
987
|
+
if "不适用条件" not in joined:
|
|
988
|
+
missing.append(f.name)
|
|
989
|
+
return missing, len(funcs)
|
|
990
|
+
|
|
991
|
+
# ---------- ⑥ 集成测试 ----------
|
|
992
|
+
def _check_integration(self, req: VerifyRequest) -> Dict[str, Any]:
|
|
993
|
+
"""依赖单元组装 + 端到端运行(简化:deps 代码拼接后执行)。"""
|
|
994
|
+
if not req.deps:
|
|
995
|
+
return {"level": "集成", "ok": True, "evidence": "无依赖(跳过)"}
|
|
996
|
+
try:
|
|
997
|
+
# 组装依赖 + 被测代码
|
|
998
|
+
combined = "\n\n".join(req.deps) + "\n\n" + req.code
|
|
999
|
+
ns: Dict[str, Any] = {}
|
|
1000
|
+
exec(compile(combined, "<integrate>", "exec"), ns)
|
|
1001
|
+
|
|
1002
|
+
# 端到端:跑第一个样例
|
|
1003
|
+
if req.cases:
|
|
1004
|
+
func_name = None
|
|
1005
|
+
tree = ast.parse(req.code)
|
|
1006
|
+
funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
|
|
1007
|
+
func_name = funcs[0].name if funcs else None
|
|
1008
|
+
if func_name and func_name in ns:
|
|
1009
|
+
inp, exp = req.cases[0]
|
|
1010
|
+
got = ns[func_name](inp) if not isinstance(inp, tuple) else ns[func_name](*inp)
|
|
1011
|
+
if got != exp:
|
|
1012
|
+
return {"level": "集成", "ok": False,
|
|
1013
|
+
"evidence": f"组装后首样例失败: {inp} → {got}(期望 {exp})"}
|
|
1014
|
+
return {"level": "集成", "ok": True,
|
|
1015
|
+
"evidence": f"{len(req.deps)} 个依赖组装 + 端到端通过"}
|
|
1016
|
+
except Exception as e:
|
|
1017
|
+
return {"level": "集成", "ok": False,
|
|
1018
|
+
"evidence": f"组装失败: {e}"}
|
|
1019
|
+
|
|
1020
|
+
|
|
1021
|
+
# ============ 四、CLI ============
|
|
1022
|
+
|
|
1023
|
+
def _build_request_from_file(path: str) -> VerifyRequest:
|
|
1024
|
+
"""从 JSON 文件构建校验请求。"""
|
|
1025
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
1026
|
+
data = json.load(f)
|
|
1027
|
+
return VerifyRequest(
|
|
1028
|
+
task=data.get("task", ""),
|
|
1029
|
+
code=data.get("code", ""),
|
|
1030
|
+
unit_id=data.get("unit_id", ""),
|
|
1031
|
+
lang=data.get("lang", "python"),
|
|
1032
|
+
cases=[tuple(c) for c in data.get("cases", [])],
|
|
1033
|
+
deps=data.get("deps", []),
|
|
1034
|
+
expected_structure=data.get("structure", {}),
|
|
1035
|
+
)
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
def _self_test() -> bool:
|
|
1039
|
+
"""自测:排序单元应通过,错误代码应失败。"""
|
|
1040
|
+
v = Verifier()
|
|
1041
|
+
passed = 0
|
|
1042
|
+
|
|
1043
|
+
# 正确代码(含中文注释——条件论注释规范 R1)
|
|
1044
|
+
good_code = (
|
|
1045
|
+
"def solve(arr):\n"
|
|
1046
|
+
" # 排序:冒泡法(相邻比较交换,最小元素浮到头部)\n"
|
|
1047
|
+
" n = len(arr)\n"
|
|
1048
|
+
" for i in range(n):\n"
|
|
1049
|
+
" for j in range(n-1-i):\n"
|
|
1050
|
+
" if arr[j] > arr[j+1]:\n"
|
|
1051
|
+
" arr[j], arr[j+1] = arr[j+1], arr[j]\n"
|
|
1052
|
+
" return arr\n"
|
|
1053
|
+
)
|
|
1054
|
+
r1 = v.verify(VerifyRequest(
|
|
1055
|
+
task="排序", code=good_code, unit_id="test_sort",
|
|
1056
|
+
cases=[([3, 1, 2], [1, 2, 3]), ([], []), ([1], [1])],
|
|
1057
|
+
))
|
|
1058
|
+
print(f"[1] 排序正确代码: {r1.summary()}")
|
|
1059
|
+
passed += 1 if r1.ok else 0
|
|
1060
|
+
|
|
1061
|
+
# 错误代码(冒泡逻辑反了)
|
|
1062
|
+
bad_code = good_code.replace("if arr[j] > arr[j+1]", "if arr[j] < arr[j+1]")
|
|
1063
|
+
r2 = v.verify(VerifyRequest(
|
|
1064
|
+
task="排序", code=bad_code, unit_id="test_sort_bad",
|
|
1065
|
+
cases=[([3, 1, 2], [1, 2, 3]), ([], []), ([1], [1])],
|
|
1066
|
+
))
|
|
1067
|
+
print(f"[2] 排序错误代码: {r2.summary()}")
|
|
1068
|
+
passed += 1 if not r2.ok else 0
|
|
1069
|
+
|
|
1070
|
+
# 缓存命中
|
|
1071
|
+
r3 = v.verify(VerifyRequest(
|
|
1072
|
+
task="排序", code=good_code, unit_id="test_sort",
|
|
1073
|
+
cases=[([3, 1, 2], [1, 2, 3]), ([], []), ([1], [1])],
|
|
1074
|
+
))
|
|
1075
|
+
print(f"[3] 缓存命中: cached={r3.cached} {r3.summary()}")
|
|
1076
|
+
passed += 1 if r3.cached else 0
|
|
1077
|
+
|
|
1078
|
+
# 语法错误
|
|
1079
|
+
r4 = v.verify(VerifyRequest(
|
|
1080
|
+
task="排序", code="def solve(arr):\n return ", unit_id="test_syntax",
|
|
1081
|
+
cases=[([1], [1])],
|
|
1082
|
+
))
|
|
1083
|
+
print(f"[4] 语法错误: {r4.summary()}")
|
|
1084
|
+
passed += 1 if not r4.ok else 0
|
|
1085
|
+
|
|
1086
|
+
# 规范符合性(排序但无比较结构)
|
|
1087
|
+
r5 = v.verify(VerifyRequest(
|
|
1088
|
+
task="排序", code="def solve(arr):\n return arr", unit_id="test_nosort",
|
|
1089
|
+
cases=[([3, 1, 2], [3, 1, 2])],
|
|
1090
|
+
))
|
|
1091
|
+
print(f"[5] 规范不符(排序无比较): {r5.summary()}")
|
|
1092
|
+
passed += 1 if not r5.ok else 0
|
|
1093
|
+
|
|
1094
|
+
print(f"\n自测: {passed}/5 通过")
|
|
1095
|
+
return passed == 5
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
def _audit_all() -> None:
|
|
1099
|
+
"""全量审计:六域单元库 → 本地校验器 → 报告(通过率/失败清单/缓存/耗时)。"""
|
|
1100
|
+
from compiler_code_units import COMPILER_UNITS
|
|
1101
|
+
from python_code_units import PYTHON_UNITS
|
|
1102
|
+
from graph_db_units import GRAPH_UNITS
|
|
1103
|
+
from os_units import OS_UNITS
|
|
1104
|
+
from browser_units import BROWSER_UNITS
|
|
1105
|
+
from net_units import NET_UNITS
|
|
1106
|
+
domains = [('compiler', COMPILER_UNITS), ('pylang', PYTHON_UNITS),
|
|
1107
|
+
('graph', GRAPH_UNITS), ('os', OS_UNITS),
|
|
1108
|
+
('browser', BROWSER_UNITS), ('net', NET_UNITS)]
|
|
1109
|
+
# 语言单元表(白箱自举主干):改造前**不在审计面**——`_audit_all` 只遍历六域
|
|
1110
|
+
# `*_units.py`,而模板生产的 CODE_UNITS/RUST_UNITS/JS_UNITS 从不进报告。
|
|
1111
|
+
# 「规范没被执行」正是这样隐身的:生成端零契约 + 审计端不看它。
|
|
1112
|
+
from code_compose import CODE_UNITS, RUST_UNITS, JS_UNITS
|
|
1113
|
+
lang_domains = [('code-py', CODE_UNITS), ('code-rust', RUST_UNITS),
|
|
1114
|
+
('code-js', JS_UNITS)]
|
|
1115
|
+
t0 = time.time()
|
|
1116
|
+
v = Verifier()
|
|
1117
|
+
total = ok = 0
|
|
1118
|
+
fails = []
|
|
1119
|
+
by_domain = {}
|
|
1120
|
+
ccg_total = ccg_missing = 0
|
|
1121
|
+
ccg_gaps = [] # [(uid, r3_missing, r4_missing)] —— 软模式缺失清单
|
|
1122
|
+
for dname, units in domains:
|
|
1123
|
+
d_ok = d_fail = 0
|
|
1124
|
+
for uid, u in units.items():
|
|
1125
|
+
total += 1
|
|
1126
|
+
req = VerifyRequest(
|
|
1127
|
+
task=u['task'], code=u['pattern'], unit_id=uid,
|
|
1128
|
+
cases=list(u.get('cases', [])),
|
|
1129
|
+
expected_structure={'inject': True} if u.get('needs_inject') else {})
|
|
1130
|
+
r = v.verify(req)
|
|
1131
|
+
# CCG 六要素(软模式)汇总:R3/R4 缺失**不拦截**,故不能只看 r.ok——
|
|
1132
|
+
# 必须单独取「规范符合性」check 里的结构化 ccg 块;否则「规范未执行」
|
|
1133
|
+
# 会再次隐身(改造前 evidence 恒写「条件论注释通过」,无从发现)。
|
|
1134
|
+
spec = next((c for c in r.checks if isinstance(c, dict)
|
|
1135
|
+
and c.get("level") == "规范符合性"), None)
|
|
1136
|
+
st = (spec or {}).get("ccg") or {}
|
|
1137
|
+
if st.get("r3_total"):
|
|
1138
|
+
ccg_total += st["r3_total"]
|
|
1139
|
+
ccg_missing += len(st.get("r3_missing") or [])
|
|
1140
|
+
if st.get("r3_missing") or st.get("r4_missing"):
|
|
1141
|
+
ccg_gaps.append((uid, st.get("r3_missing") or [],
|
|
1142
|
+
st.get("r4_missing") or []))
|
|
1143
|
+
if r.ok:
|
|
1144
|
+
ok += 1; d_ok += 1
|
|
1145
|
+
else:
|
|
1146
|
+
fails.append((uid, r.reason[:80])); d_fail += 1
|
|
1147
|
+
by_domain[dname] = (d_ok, d_fail)
|
|
1148
|
+
s = v.cache.stats()
|
|
1149
|
+
print(f"=== 全量审计(verifier v{VERIFIER_VERSION}): {ok}/{total} 通过 "
|
|
1150
|
+
f"({100.0 * ok / total:.1f}%) 耗时 {time.time() - t0:.1f}s ===")
|
|
1151
|
+
print("各域:", by_domain)
|
|
1152
|
+
# CCG 六要素报告必须**显式打印**:单元"校验通过"只代表 L1-L3 + R1/R2 过了,
|
|
1153
|
+
# 六要素接口契约是否达成是另一件事——两者混为一谈正是问题根源。
|
|
1154
|
+
rate = (100.0 * (ccg_total - ccg_missing) / ccg_total) if ccg_total else 0.0
|
|
1155
|
+
print(f"CCG 三要素(R3 软模式): {ccg_total - ccg_missing}/{ccg_total} 符号达标"
|
|
1156
|
+
f"({rate:.1f}%) | 有缺失的单元 {len(ccg_gaps)}/{total} 个")
|
|
1157
|
+
if ccg_gaps:
|
|
1158
|
+
print("--- CCG 注释缺失清单(软模式,不拦截)---")
|
|
1159
|
+
for uid, m3, m4 in ccg_gaps:
|
|
1160
|
+
print(f"[{uid}] R3缺={m3[:6]} R4缺={m4[:4]}")
|
|
1161
|
+
# --- 语言单元表(自举主干)CCG 契约审计 ---
|
|
1162
|
+
# 与六域**分开统计**,原因有二:
|
|
1163
|
+
# ① RUST/JS 模板非 Python 语法,L1 语法必失败属正常(校验器与目标语言
|
|
1164
|
+
# 不匹配),此处只问「契约是否写进模板」——走文本判据
|
|
1165
|
+
# (`_check_comment_spec_text`,mode="text");
|
|
1166
|
+
# ② CODE_UNITS 是 Python 模板但含 `{fn}` 占位符(未替换前非法 Python),
|
|
1167
|
+
# v9 起由 `_render_placeholders` 展开后走**全量 Python 判据**
|
|
1168
|
+
# (mode="python":R1 逐符号中文注释 + R2 注释语境 + R3/R4),
|
|
1169
|
+
# 故本节 `params` 必须透传单元声明的占位符名。
|
|
1170
|
+
# 两档判据强度不同但**达标口径一致**(三要素 + 不适用条件齐备)。
|
|
1171
|
+
lang_total = lang_ok = 0
|
|
1172
|
+
lang_gaps = []
|
|
1173
|
+
lang_modes = {} # 判据档位计数(python=全量判据 / text=文本判据)
|
|
1174
|
+
for dname, units in lang_domains:
|
|
1175
|
+
for uid, u in units.items():
|
|
1176
|
+
lang_total += 1
|
|
1177
|
+
r = v.verify(VerifyRequest(task=u.get('task', ''), code=u['pattern'],
|
|
1178
|
+
unit_id=uid, cases=[],
|
|
1179
|
+
expected_structure={
|
|
1180
|
+
'params': u.get('params') or ['fn']}))
|
|
1181
|
+
spec = next((c for c in r.checks if isinstance(c, dict)
|
|
1182
|
+
and c.get("level") == "规范符合性"), None) or {}
|
|
1183
|
+
st = spec.get("ccg") or {}
|
|
1184
|
+
r3m = st.get("r3_missing") or []
|
|
1185
|
+
r4m = st.get("r4_missing") or []
|
|
1186
|
+
md = st.get("mode", "python")
|
|
1187
|
+
lang_modes[md] = lang_modes.get(md, 0) + 1
|
|
1188
|
+
# 「达标」= 判据产出 + 无硬失败 + 契约齐备,三者缺一不可——
|
|
1189
|
+
# 只统计 ccg 缺失会漏报 R1/R2 硬失败(spec.ok=False 但 ccg 齐备
|
|
1190
|
+
# 仍会被算作达标)= 新的谎报面(同 v7「没检查冒充检查通过」同构)。
|
|
1191
|
+
if not st.get("r3_total"):
|
|
1192
|
+
lang_gaps.append((dname, uid, md, "判据未产出(ccg 块为空)"))
|
|
1193
|
+
elif not spec.get("ok"):
|
|
1194
|
+
lang_gaps.append((dname, uid, md,
|
|
1195
|
+
f"硬失败: {str(spec.get('evidence'))[:90]}"))
|
|
1196
|
+
elif r3m or r4m:
|
|
1197
|
+
lang_gaps.append((dname, uid, md, f"R3缺={r3m} R4缺={r4m}"))
|
|
1198
|
+
else:
|
|
1199
|
+
lang_ok += 1
|
|
1200
|
+
lrate = (100.0 * lang_ok / lang_total) if lang_total else 0.0
|
|
1201
|
+
print(f"=== 语言单元表 CCG 契约: {lang_ok}/{lang_total} 达标({lrate:.1f}%)"
|
|
1202
|
+
f" | 判据档位 {lang_modes}(python=展开占位符后全量判据 / text=文本判据)===")
|
|
1203
|
+
for dname, uid, md, desc in lang_gaps:
|
|
1204
|
+
print(f" [{dname}/{uid}] mode={md} {desc}")
|
|
1205
|
+
print(f"缓存: 共 {s['total']} 条(通过 {s['passed']} / 失败 {s['failed']})"
|
|
1206
|
+
f" | 本进程命中 {s['hits']} / 未命中 {s['misses']}(命中率 {s['hit_rate']}%)")
|
|
1207
|
+
if fails:
|
|
1208
|
+
print("--- 失败清单 ---")
|
|
1209
|
+
for uid, reason in fails:
|
|
1210
|
+
print(f"[{uid}] {reason}")
|
|
1211
|
+
|
|
1212
|
+
|
|
1213
|
+
def _cost_model(daily_tokens: float = 1.3e9, hit_rate: float = 0.999,
|
|
1214
|
+
price_hit: float = 2.0, price_full: float = 2.0,
|
|
1215
|
+
days: int = 30) -> None:
|
|
1216
|
+
"""本地化成本对照模型(阶段 5 替换测算器)。
|
|
1217
|
+
|
|
1218
|
+
「原 LLM 校验环节」= 白箱自举的外部校准角色(LLM 查表校验):白箱自举
|
|
1219
|
+
总消耗 13 亿 token(用户实测),其中 99.9% 是重复查表校验(反复读同样
|
|
1220
|
+
的单元/代码/规则表——即 LLM 缓存命中)。本地校验器(六层确定性规则 +
|
|
1221
|
+
sha256 指纹缓存)接管后,这部分 token 归零;仅剩 0.1% 为真·新任务
|
|
1222
|
+
(新单元设计/语义校准)保留 LLM 价值。
|
|
1223
|
+
|
|
1224
|
+
默认参数取自设计文档实测:13 亿 token/天、99.9% 缓存命中、GLM 缓存价
|
|
1225
|
+
2 元/百万。原方案:全部 token 经 LLM(命中按缓存价计费);
|
|
1226
|
+
本地化后:命中部分走本地磁盘(≈0 成本),仅未命中部分保留 LLM 兜底。
|
|
1227
|
+
"""
|
|
1228
|
+
per_m = 1e6
|
|
1229
|
+
orig = daily_tokens * (hit_rate * price_hit + (1 - hit_rate) * price_full) / per_m
|
|
1230
|
+
local = daily_tokens * (1 - hit_rate) * price_full / per_m
|
|
1231
|
+
saved = orig - local
|
|
1232
|
+
print(f"=== 本地化成本对照(LLM 查表校验 → 本地校验缓存)===")
|
|
1233
|
+
print(f"背景: 白箱自举外部校准(LLM 查表校验)日消耗 {daily_tokens:.3g} token,"
|
|
1234
|
+
f"其中 {hit_rate*100:.1f}% 为重复查表(缓存命中)")
|
|
1235
|
+
print(f"输入: 每日 token {daily_tokens:.3g} | 命中率 {hit_rate*100:.1f}% "
|
|
1236
|
+
f"| 命中价 {price_hit} 元/百万 | 未命中价 {price_full} 元/百万")
|
|
1237
|
+
print(f"原方案(LLM 缓存命中付费): {orig:,.2f} 元/天 ≈ {orig*days:,.0f} 元/{days}天")
|
|
1238
|
+
print(f"本地化后(命中零成本,仅未命中 LLM 兜底): {local:,.2f} 元/天 "
|
|
1239
|
+
f"≈ {local*days:,.0f} 元/{days}天")
|
|
1240
|
+
print(f"节省: {saved:,.2f} 元/天 ≈ {saved*days:,.0f} 元/{days}天 "
|
|
1241
|
+
f"(省 {(100*saved/orig if orig else 0):.1f}%)")
|
|
1242
|
+
print(f"对照: 白箱自举 {daily_tokens:.3g} token 中 "
|
|
1243
|
+
f"{daily_tokens*hit_rate:.3g} token({hit_rate*100:.1f}%)为重复查表校验,"
|
|
1244
|
+
f"由 verifier 本地缓存接管后 token 归零")
|
|
1245
|
+
if hit_rate == 1.0:
|
|
1246
|
+
print("命中率 100% → 本地化后每日成本归零(磁盘 I/O 可忽略)")
|
|
1247
|
+
|
|
1248
|
+
|
|
1249
|
+
def main() -> None:
|
|
1250
|
+
import argparse
|
|
1251
|
+
ap = argparse.ArgumentParser(description="白箱本地校验器(零 LLM)")
|
|
1252
|
+
ap.add_argument("--verify", type=str, help="校验请求 JSON 文件路径")
|
|
1253
|
+
ap.add_argument("--cache-stats", action="store_true", help="缓存统计")
|
|
1254
|
+
ap.add_argument("--audit", action="store_true", help="全量审计(六域单元)")
|
|
1255
|
+
ap.add_argument("--cost-model", action="store_true", help="本地化成本对照模型")
|
|
1256
|
+
ap.add_argument("--tokens", type=float, default=1.3e9, help="每日 token 量")
|
|
1257
|
+
ap.add_argument("--hit-rate", type=float, default=0.999, help="缓存命中率")
|
|
1258
|
+
ap.add_argument("--price-hit", type=float, default=2.0, help="命中价 元/百万")
|
|
1259
|
+
ap.add_argument("--price-full", type=float, default=2.0, help="未命中价 元/百万")
|
|
1260
|
+
ap.add_argument("--days", type=int, default=30, help="测算天数")
|
|
1261
|
+
ap.add_argument("--self-test", action="store_true", help="自测")
|
|
1262
|
+
args = ap.parse_args()
|
|
1263
|
+
|
|
1264
|
+
if args.self_test:
|
|
1265
|
+
sys.exit(0 if _self_test() else 1)
|
|
1266
|
+
|
|
1267
|
+
if args.cost_model:
|
|
1268
|
+
_cost_model(args.tokens, args.hit_rate, args.price_hit,
|
|
1269
|
+
args.price_full, args.days)
|
|
1270
|
+
return
|
|
1271
|
+
|
|
1272
|
+
if args.audit:
|
|
1273
|
+
_audit_all()
|
|
1274
|
+
return
|
|
1275
|
+
|
|
1276
|
+
if args.cache_stats:
|
|
1277
|
+
stats = VerifyCache().stats()
|
|
1278
|
+
sv = VerifyCache().savings()
|
|
1279
|
+
print(f"缓存统计: 共 {stats['total']} 条(通过 {stats['passed']} / 失败 {stats['failed']})")
|
|
1280
|
+
print(f"命中计数: 本进程命中 {stats['hits']} / 未命中 {stats['misses']}(命中率 {stats['hit_rate']}%)")
|
|
1281
|
+
print(f"审计日志: {sv['requests']} 次校验(缓存命中 {sv['hits']} 次)")
|
|
1282
|
+
print(f"减少的 LLM 输入: 累计 {sv['saved_chars']:,} 字符"
|
|
1283
|
+
f" ≈ {sv['saved_tokens']:,} token(表+请求内容经本地处理,不再输入 LLM)")
|
|
1284
|
+
print("(每次命中 = 一次本该发生的 LLM 查表 → 本地零计算返回)")
|
|
1285
|
+
return
|
|
1286
|
+
|
|
1287
|
+
if args.verify:
|
|
1288
|
+
req = _build_request_from_file(args.verify)
|
|
1289
|
+
result = Verifier().verify(req)
|
|
1290
|
+
print(json.dumps(asdict(result), ensure_ascii=False, indent=1))
|
|
1291
|
+
return
|
|
1292
|
+
|
|
1293
|
+
ap.print_help()
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
if __name__ == "__main__":
|
|
1297
|
+
main()
|