@furongjun1999/dsh-memory 0.4.7 → 0.4.9
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 +130 -47
- package/codebuddy/CODEBUDDY.md +21 -10
- package/codebuddy/README.md +13 -1
- package/codebuddy/mcp.json +9 -0
- 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 -0
- 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 -0
- package/docs/README.md +111 -0
- package/docs/discipline/harnesses.yaml +226 -152
- package/docs/discipline/templates/full.md.tmpl +61 -58
- package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_LoCoMo_/347/201/265/346/236/242_.md +127 -0
- package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_LoCoMo_/347/201/265/346/236/242_.png +0 -0
- package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_/347/201/265/346/236/242_vs_dejavu_/347/273/237/344/270/200/350/257/204/345/210/206_v7.md +202 -0
- package/docs/experiments/linkref_backfill/candidates_20260917.json +726 -0
- package/docs/experiments/linkref_backfill/candidates_internal_20260917.json +602 -0
- package/docs/experiments/linkref_backfill/candidates_internal_v2.json +603 -0
- package/docs/experiments/linkref_backfill/candidates_secret_20260917.json +884 -0
- package/docs/experiments/linkref_backfill/candidates_secret_v2.json +789 -0
- package/docs/experiments/m4-role-probe/census.py +86 -0
- package/docs/experiments/m4-role-probe/probe.py +89 -0
- package/docs/experiments/mapped_confidence/bench_mapped_conf.py +216 -0
- package/docs/experiments/mapped_confidence/post_check.py +75 -0
- package/docs/experiments/mapped_confidence/repro_thirdparty_tol.py +83 -0
- package/docs/experiments/mapped_confidence/result_mapped_conf.json +215 -0
- package/docs/hive//345/244/232/347/253/257harness/351/200/232/344/277/241/345/245/221/347/272/246_v0.1.md +42 -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 -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 -0
- package/docs/hive//347/234/237/345/256/236/345/272/223/347/253/257/345/210/260/347/253/257/345/256/236/346/265/213_S1b/344/270/216/345/217/254/345/233/236/346/235/203/350/241/241_v0.1.md +57 -0
- package/docs/hive//347/234/237/345/256/236/345/272/223/347/253/257/345/210/260/347/253/257/345/256/236/346/265/213_S7/345/200/222/346/216/222/345/200/231/351/200/211/345/261/202_v0.1.md +126 -0
- package/docs/hive//350/234/202/345/267/242/345/217/214/345/256/236/344/276/213/344/272/222/351/252/214_/350/256/276/350/256/241/345/256/232/347/250/277.md +503 -0
- 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 +14 -2
- package/docs/mdcg/D_meta_/345/267/245/347/250/213/345/214/226/346/226/271/346/241/210_v0.2.md +216 -0
- package/docs/{README → mdcg/README}/350/257/246/347/273/206/347/211/210_v0.4.5.md +631 -625
- package/docs/{ → mdcg/}/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 +1 -1
- package/docs/mdcg//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_/345/245/221/347/272/246_v0.1.md +82 -0
- 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 -0
- 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 +263 -0
- 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 +1 -1
- package/docs/mdcg//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 -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 +2 -2
- package/docs/{ → mdcg/}/347/201/265/346/236/242/344/270/211/345/261/202/346/213/206/345/210/206/350/247/204/345/210/222_v0.1.md +181 -181
- 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 -0
- package/docs/mdcg//347/216/257/344/272/214_/347/231/275/347/256/261/345/241/253/345/205/205/346/265/201/346/260/264/347/272/277_v0.1.md +31 -0
- package/docs/{ → mdcg/}/347/274/272/345/217/243/345/215/225_P0/346/224/266/345/217/243_v0.1.md +2 -2
- 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 +1 -1
- 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 +4 -4
- package/docs/{ → mdcg/}/350/256/260/345/277/206/346/223/215/344/275/234/347/263/273/347/273/237_MdCGOS/344/270/216MCP/346/216/245/345/205/245_v0.1.md +2 -2
- package/docs/mdcg//350/267/250/347/253/257/351/252/214/350/257/201/344/270/216/345/220/214/346/255/245/345/215/217/350/256/256_v0.1.md +93 -0
- 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 -0
- package/docs/{ → swarm/}/350/234/202/347/276/244/344/272/222/350/201/224_v0.1.md +3 -3
- 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 -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 -0
- 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 -0
- 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 -0
- 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 +6 -6
- 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 +1 -1
- 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 -0
- 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 -0
- package/docs//345/255/230/347/256/227/344/270/200/344/275/223/346/236/266/346/236/204/345/255/246/344/271/240_/345/244/226/351/203/250/347/220/206/350/256/272/345/257/271/347/205/247/344/270/216/350/267/257/347/272/277/344/272/244/346/216/245_20260916.md +98 -0
- package/docs//345/255/230/347/256/227/344/270/200/344/275/223/347/245/236/347/273/217/347/275/221/347/273/234/346/236/266/346/236/204_/346/200/273/347/272/262/344/270/216/344/272/244/346/216/245_20260916.md +109 -0
- 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 +434 -405
- package/docs//347/201/265/346/236/242/350/207/252/346/210/221/346/224/271/350/277/233/345/267/245/344/275/234/350/256/241/345/210/222_/345/244/226/351/203/250/347/240/224/347/251/266/347/263/273/345/210/227/345/220/270/346/224/266_v1_20260919.md +286 -0
- package/docs//347/231/275/347/256/261/345/214/226/347/272/262/351/242/206_/344/270/211/351/241/271/347/233/256/345/255/246/344/271/240/346/200/273/346/236/266/346/236/204/344/270/216Ghidra/344/272/244/346/216/245_20260916.md +104 -0
- package/docs//350/256/260/345/277/206/350/257/204/345/256/241/347/263/273/347/273/237_/347/253/213/351/241/271/350/256/276/350/256/241/344/270/216/346/226/275/345/267/245/344/272/244/346/216/245_20260915.md +183 -0
- package/dsh/README.md +34 -1
- package/dsh/cordis.yml.example +13 -7
- package/dsh/hive-mcp-probe.mjs +94 -0
- package/dsh/hive-mcp.example.yml +62 -0
- package/dsh/update-lingshu.bat +11 -0
- package/dsh/update-lingshu.ps1 +337 -0
- package/lib/hooks.d.ts +8 -0
- package/lib/hooks.js +27 -24
- package/lib/index.d.ts +4 -2
- package/lib/index.js +29 -6
- package/lib/lib/datapath.d.ts +76 -1
- package/lib/lib/datapath.js +199 -13
- package/lib/lib/mdcg_client.d.ts +6 -3
- package/lib/lib/mdcg_client.js +6 -5
- package/lib/lib/mutual.js +4 -4
- package/lib/lib/prompt_safety.d.ts +48 -0
- package/lib/lib/prompt_safety.js +62 -0
- package/lib/lib/token_store.js +4 -5
- package/md_cg/audit.py +91 -2
- package/md_cg/autonomy.py +86 -15
- package/md_cg/backfill.py +36 -1
- package/md_cg/backfill_bigdomain.py +34 -0
- package/md_cg/bench6_arms.py +28 -1
- package/md_cg/bench6_common.py +10 -1
- package/md_cg/bench6_competitors.py +6 -1
- package/md_cg/bench_axis_domain.py +9 -1
- package/md_cg/bench_blind_comp.py +7 -1
- package/md_cg/bench_en_atoms_public.py +9 -0
- package/md_cg/bench_governance.py +348 -0
- package/md_cg/bench_lme_zh.py +16 -1
- package/md_cg/bench_locomo.py +2 -1
- package/md_cg/bench_locomo_zh.py +16 -1
- package/md_cg/bench_locomo_zh_public.py +4 -1
- package/md_cg/bench_longmem.py +2 -1
- package/md_cg/bench_membench.py +37 -6
- package/md_cg/bench_p0.py +4 -1
- package/md_cg/bench_progressive.py +61 -10
- package/md_cg/bench_role_views.py +238 -0
- package/md_cg/bench_task_ab.py +8 -1
- package/md_cg/bench_task_ab_llm.py +13 -1
- package/md_cg/bench_unified_en.py +6 -1
- package/md_cg/bench_zh_mad.py +20 -1
- package/md_cg/blindspot_tickets.py +123 -0
- package/md_cg/branches.py +286 -0
- package/md_cg/build_postings.py +73 -0
- package/md_cg/ccgc.py +949 -0
- package/md_cg/census.py +5 -1
- package/md_cg/chain.py +24 -3
- package/md_cg/codeindex.py +134 -17
- package/md_cg/coldverify.py +265 -0
- package/md_cg/comment_gate.py +338 -0
- package/md_cg/cond_compose.py +190 -0
- package/md_cg/cond_facts.py +155 -0
- package/md_cg/cond_template.json +107 -0
- package/md_cg/condition_anchor.py +143 -0
- package/md_cg/conformance.py +727 -0
- package/md_cg/consistency.py +25 -2
- package/md_cg/consolidate.py +53 -2
- package/md_cg/corpus.py +5 -1
- package/md_cg/crosscheck.py +42 -2
- package/md_cg/crypto.py +35 -1
- package/md_cg/d_meta.py +310 -0
- package/md_cg/datapath.py +201 -26
- package/md_cg/docindex.py +122 -1
- package/md_cg/eval_common.py +35 -6
- package/md_cg/evidence.py +27 -1
- package/md_cg/evolution.py +21 -1
- package/md_cg/export.py +11 -1
- package/md_cg/forgetting.py +34 -4
- package/md_cg/fsutil.py +81 -2
- package/md_cg/hotcache.py +214 -0
- package/md_cg/hyperedge.py +251 -0
- package/md_cg/identity.py +20 -3
- package/md_cg/insight.py +19 -3
- package/md_cg/lexicon/build_cedict_en_zh.py +9 -0
- package/md_cg/lexicon/build_standard_en.py +171 -168
- package/md_cg/lexicon/expand_en_zh.py +6 -0
- package/md_cg/lifecycle.py +273 -0
- package/md_cg/linkref.py +281 -0
- package/md_cg/links.py +29 -1
- package/md_cg/mcp_server.py +894 -168
- package/md_cg/md_whitebox.py +53 -1
- package/md_cg/mdcg.py +1205 -41
- package/md_cg/mdcos.py +1060 -76
- package/md_cg/metacognition.py +39 -4
- package/md_cg/migrate.py +4 -0
- package/md_cg/migrate_aeis.py +221 -213
- package/md_cg/migrate_roleplay.py +8 -0
- package/md_cg/migrate_wisdom_graph.py +14 -1
- package/md_cg/mreview/__init__.py +25 -0
- package/md_cg/mreview/__main__.py +110 -0
- package/md_cg/mreview/bundle.py +178 -0
- package/md_cg/mreview/candidates.py +262 -0
- package/md_cg/mreview/govern.py +694 -0
- package/md_cg/mreview/locate.py +939 -0
- package/md_cg/mreview/pipeline.py +729 -0
- package/md_cg/mreview/rules/duplication.json +21 -0
- package/md_cg/mreview/rules/field_coverage.json +54 -0
- package/md_cg/mreview/rules/source_license.json +21 -0
- package/md_cg/mreview/rules/template_flow.json +21 -0
- package/md_cg/mreview/ruleset.py +253 -0
- package/md_cg/nodefile.py +269 -1
- package/md_cg/pooling.py +23 -1
- package/md_cg/postings.py +298 -0
- package/md_cg/predict.py +113 -15
- package/md_cg/progressive.py +3 -0
- package/md_cg/protect.py +18 -5
- package/md_cg/protocol.py +372 -0
- package/md_cg/provenance.py +262 -0
- package/md_cg/reach.py +453 -0
- package/md_cg/refindex.py +88 -4
- package/md_cg/refine.py +20 -1
- package/md_cg/roleviews.py +89 -0
- package/md_cg/routing.py +76 -0
- package/md_cg/scrub.py +68 -3
- package/md_cg/security.py +26 -1
- package/md_cg/self_state.py +65 -2
- package/md_cg/selfreport.py +151 -0
- package/md_cg/semantic/canonical.py +5 -0
- package/md_cg/semantic/en_normalizer.py +364 -295
- package/md_cg/semantic/zh_en_atoms.py +139 -136
- package/md_cg/signer.py +41 -1
- package/md_cg/sources.py +583 -547
- package/md_cg/statushdr.py +179 -0
- package/md_cg/stg.py +59 -18
- package/md_cg/subgraph.py +23 -0
- package/md_cg/sustain.py +74 -1
- package/md_cg/tasks.py +471 -0
- package/md_cg/test_action_derive.py +203 -0
- package/md_cg/test_audit_rotate.py +270 -0
- package/md_cg/test_autonomy.py +26 -0
- package/md_cg/test_bench_governance.py +102 -0
- package/md_cg/test_blindspot_tickets.py +166 -0
- package/md_cg/test_branches.py +249 -0
- package/md_cg/test_ccg_perturb.py +1 -1
- package/md_cg/test_ccgc.py +433 -0
- package/md_cg/test_codeindex.py +338 -0
- package/md_cg/test_comment_gate.py +187 -0
- package/md_cg/test_cond_compose_anchors.py +76 -0
- package/md_cg/test_condition_anchor.py +82 -0
- package/md_cg/test_conformance.py +343 -0
- package/md_cg/test_d_meta.py +412 -0
- package/md_cg/test_datapath_root.py +188 -0
- package/md_cg/test_en_pipeline.py +24 -0
- package/md_cg/test_gain_gate.py +47 -1
- package/md_cg/test_health_scale.py +173 -0
- package/md_cg/test_hot_cold.py +187 -0
- package/md_cg/test_hyperedge.py +245 -0
- package/md_cg/test_identity_attribution.py +6 -0
- package/md_cg/test_index_durability.py +224 -0
- package/md_cg/test_lifecycle.py +309 -0
- package/md_cg/test_linkref.py +306 -0
- package/md_cg/test_md_access_parity.py +15 -3
- package/md_cg/test_mr_m1.py +769 -0
- package/md_cg/test_mr_m2.py +587 -0
- package/md_cg/test_mr_m3.py +710 -0
- package/md_cg/test_mr_m4.py +485 -0
- package/md_cg/test_p1.py +1 -1
- package/md_cg/test_p11_consistency.py +1 -1
- package/md_cg/test_p12_metacognition.py +2 -2
- package/md_cg/test_p16_self_state.py +1 -1
- package/md_cg/test_p26_refindex.py +50 -21
- package/md_cg/test_p27_docindex.py +258 -4
- package/md_cg/test_p28_refcheck.py +1 -1
- package/md_cg/test_p29_session_ingest_export.py +1 -1
- package/md_cg/test_p2_mcp.py +7 -1
- package/md_cg/test_p31_insight.py +24 -0
- package/md_cg/test_p38_contextualize.py +1 -1
- package/md_cg/test_p39_vision_evidence.py +1 -1
- package/md_cg/test_p40_refine_worklist.py +1 -1
- package/md_cg/test_p41_evolve_patrol.py +1 -1
- package/md_cg/test_p42_provenance.py +1 -1
- package/md_cg/test_p43_pooling.py +41 -13
- package/md_cg/test_p44_md_whitebox.py +14 -1
- package/md_cg/test_protocol.py +243 -0
- package/md_cg/test_reach.py +378 -0
- package/md_cg/test_reach_keys.py +201 -0
- package/md_cg/test_reach_meta_exits.py +145 -0
- package/md_cg/test_read_clip.py +141 -0
- package/md_cg/test_retr_s1.py +340 -0
- package/md_cg/test_retr_s1b.py +209 -0
- package/md_cg/test_retr_s3.py +194 -0
- package/md_cg/test_retr_s4.py +163 -0
- package/md_cg/test_retr_s5.py +200 -0
- package/md_cg/test_retr_s6.py +157 -0
- package/md_cg/test_retr_s7.py +385 -0
- package/md_cg/test_retr_s8_time.py +316 -0
- package/md_cg/test_retr_s9_edges.py +286 -0
- package/md_cg/test_retr_s9_entity_ctx.py +175 -0
- package/md_cg/test_review_conformance.py +367 -0
- package/md_cg/test_role_views.py +354 -0
- package/md_cg/test_tasks.py +409 -0
- package/md_cg/test_tool_face.py +189 -0
- package/md_cg/test_trust.py +361 -0
- package/md_cg/test_twophase.py +286 -0
- package/md_cg/test_units_poll.py +71 -0
- package/md_cg/test_v14_fixes.py +397 -0
- package/md_cg/test_validity_filter.py +280 -0
- package/md_cg/test_wisdom_md_store.py +7 -3
- package/md_cg/test_writepipe.py +214 -0
- package/md_cg/theory.py +17 -2
- package/md_cg/tokens.py +134 -12
- package/md_cg/tool_face.py +261 -0
- package/md_cg/trust.py +943 -0
- package/md_cg/twophase.py +232 -0
- package/md_cg/units.py +665 -0
- package/md_cg/vision_evidence.py +25 -2
- package/md_cg/weights.py +25 -2
- package/md_cg/whitebox.py +33 -2
- package/md_cg/whitebox_kb/data/verify_cache.json +21233 -360
- package/md_cg/whitebox_kb/data/verify_savings.jsonl +5124 -0
- package/md_cg/whitebox_kb/wisdom/audit_log/chain_heat.json +10 -10
- package/md_cg/whitebox_kb/wisdom/code_compose.py +113 -6
- package/md_cg/whitebox_kb/wisdom/code_solidified.json +1 -1
- package/md_cg/whitebox_kb/wisdom/verifier.py +340 -55
- package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db +0 -0
- package/md_cg/writelimit.py +37 -7
- package/md_cg/writepipe.py +543 -0
- package/package.json +2 -2
- package/skills/skills/designer-perspective/scripts/__pycache__/designer.cpython-310.pyc +0 -0
- package/skills/skills/designer-perspective/scripts/designer.py +17 -1
- package/skills/skills/designer-perspective/tests/selftest.py +3 -1
- package/src/hooks.ts +27 -22
- package/src/index.ts +33 -6
- package/src/lib/datapath.ts +211 -13
- package/src/lib/mdcg_client.ts +12 -8
- package/src/lib/mutual.ts +411 -411
- package/src/lib/prompt_safety.ts +62 -0
- package/src/lib/token_store.ts +4 -5
- package/zcode/AGENTS.md +21 -10
- package/zcode/README.md +4 -0
- package/docs//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +0 -221
- /package/docs/{AGI → 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" +0 -0
- /package/docs/{AGI → 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" +0 -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" +0 -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" +0 -0
- /package/docs/{guardrail-charter.md → mdcg/guardrail-charter.md} +0 -0
- /package/docs/{lingshu_tutorial.html → mdcg/lingshu_tutorial.html} +0 -0
- /package/docs/{memory-assessment.html → mdcg/memory-assessment.html} +0 -0
- /package/docs/{memory_score.html → mdcg/memory_score.html} +0 -0
- /package/docs/{memory_score.png → mdcg/memory_score.png} +0 -0
- /package/docs/{release_v0.3.0.md → mdcg/release_v0.3.0.md} +0 -0
- /package/docs/{release_v0.4.5.md → mdcg/release_v0.4.5.md} +0 -0
- /package/docs/{tool_table_v0.3.0.md → mdcg/tool_table_v0.3.0.md} +0 -0
- /package/docs/{ → mdcg/}/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" +0 -0
- /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" +0 -0
- /package/docs/{ → mdcg/}/347/201/265/346/236/242MCP/345/267/245/345/205/267/346/200/273/350/241/250_v3.4.md" +0 -0
- /package/docs/{ → mdcg/}/347/201/265/346/236/242_/350/207/252/346/210/221/345/261/202/345/256/232/344/271/211.md" +0 -0
- /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_MD/347/233/256/345/275/225/346/226/271/346/241/210_v0.1.md" +0 -0
- /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" +0 -0
- /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276/344/275/234/344/270/272/350/256/260/345/277/206/346/223/215/344/275/234/347/263/273/347/273/237_/350/257/204/344/274/260/344/270/216/350/267/257/347/272/277_v0.1.md" +0 -0
- /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" +0 -0
- /package/docs/{ → plans/}/347/237/245/350/257/206/345/233/276/350/260/261/351/241/271/347/233/256_/344/272/244/346/216/245/346/226/207/346/241/243_20260914.md" +0 -0
- /package/docs/{ → swarm/}/350/234/202/347/276/244/345/244/232/346/231/272/350/203/275/344/275/223_/344/275/277/347/224/250/346/212/245/345/221/212_20260913.md" +0 -0
- /package/docs/{ → swarm/}/350/234/202/347/276/244/345/244/232/346/231/272/350/203/275/344/275/223_/345/212/237/350/203/275/350/257/264/346/230/216_v0.6.md" +0 -0
- /package/docs/{ → swarm/}/350/234/202/347/276/244/347/233/262/345/214/272/346/240/207/350/256/260_v1.0.md" +0 -0
- /package/docs/{ → theory/}/344/277/241/346/201/257/345/267/256/344/270/272/344/273/200/344/271/210/345/277/205/347/204/266/345/255/230/345/234/250/344/270/224/350/207/252/347/204/266/346/211/251/345/244/247.md" +0 -0
- /package/docs/{ → theory/}/346/231/272/350/203/275/347/232/204/345/205/254/347/220/206/345/214/226/345/237/272/347/237/263.md" +0 -0
- /package/docs/{ → theory/}/346/231/272/350/203/275/347/232/204/350/256/244/347/237/245/350/277/207/347/250/213.md" +0 -0
- /package/docs/{ → theory/}/346/231/272/350/203/275/350/256/2723.4.md" +0 -0
- /package/docs/{ → theory/}/347/231/275/347/256/261/346/231/272/350/203/275/346/230/257/344/273/200/344/271/210/357/274/237.md" +0 -0
- /package/docs/{ → theory/}/347/231/275/347/256/261/346/231/272/350/203/275/347/263/273/345/210/227/302/267/347/254/254/344/272/224/347/257/207/357/274/232/350/256/251AI/347/234/237/346/255/243/350/243/205/344/270/212/350/256/260/345/277/206.md" +0 -0
package/md_cg/crosscheck.py
CHANGED
|
@@ -100,6 +100,7 @@ CONDITION_MARK = "〔来源限定〕"
|
|
|
100
100
|
|
|
101
101
|
# ---- 赛道与来源执照 ------------------------------------------------------
|
|
102
102
|
|
|
103
|
+
# 生效条件:给定 fm,若显式 track/discipline_type 命中枚举则返回对应赛道;否则用相关元数据与正文 CCG 字段匹配提示词,返回 humanities/science/undetermined。
|
|
103
104
|
def classify_track(fm: dict, content: str = "") -> str:
|
|
104
105
|
"""判定节点赛道:`humanities` / `science` / `undetermined`。
|
|
105
106
|
|
|
@@ -133,11 +134,13 @@ def classify_track(fm: dict, content: str = "") -> str:
|
|
|
133
134
|
return "undetermined"
|
|
134
135
|
|
|
135
136
|
|
|
137
|
+
# 生效条件:给定 track,返回 SOURCE_POLICY 中映射的策略名;未知 track 返回空串。
|
|
136
138
|
def source_policy(track: str) -> str:
|
|
137
139
|
"""赛道 → 来源策略名(空串表示不可判定,应 DEFER)。"""
|
|
138
140
|
return SOURCE_POLICY.get(track or "", "")
|
|
139
141
|
|
|
140
142
|
|
|
143
|
+
# 生效条件:给定 track,若为 science 返回 REPRODUCIBLE_BASIS,若为 humanities 返回 CONSISTENCY_BASIS,否则返回 ()。
|
|
141
144
|
def allowed_basis(track: str) -> tuple:
|
|
142
145
|
if track == "science":
|
|
143
146
|
return tuple(nodefile.REPRODUCIBLE_BASIS)
|
|
@@ -146,15 +149,18 @@ def allowed_basis(track: str) -> tuple:
|
|
|
146
149
|
return ()
|
|
147
150
|
|
|
148
151
|
|
|
152
|
+
# 生效条件:给定 track 与 basis,当 basis 非空且其字符串形式属于 allowed_basis(track) 时返回 True,否则 False。
|
|
149
153
|
def basis_licensed(track: str, basis) -> bool:
|
|
150
154
|
"""来源执照:理科要可复现证据,文科要来源一致性;赛道未定一律不发放。"""
|
|
151
155
|
return bool(basis) and str(basis) in allowed_basis(track)
|
|
152
156
|
|
|
153
157
|
|
|
158
|
+
# 生效条件:给定 field,返回 FIELD_NORMALIZE 映射值;未知字段返回空串。
|
|
154
159
|
def normalize_field(field) -> str:
|
|
155
160
|
return FIELD_NORMALIZE.get(str(field or "").strip(), "")
|
|
156
161
|
|
|
157
162
|
|
|
163
|
+
# 生效条件:给定 v,若为 None 返回 [];否则将单值或列表转为去除空白后非空字符串的列表。
|
|
158
164
|
def _as_source(v) -> list:
|
|
159
165
|
if v is None:
|
|
160
166
|
return []
|
|
@@ -164,6 +170,7 @@ def _as_source(v) -> list:
|
|
|
164
170
|
|
|
165
171
|
# ---- B 型识别与条件化改写 ------------------------------------------------
|
|
166
172
|
|
|
173
|
+
# 生效条件:给定 text,若含 VALUATION_MARKERS 或匹配 VALUATION_PATTERNS 则返回 B_CLAIM,否则 A_CLAIM。
|
|
167
174
|
def claim_type(text) -> str:
|
|
168
175
|
"""`A_fact`(事实性)或 `B_valuation`(评价性断言)。"""
|
|
169
176
|
s = str(text or "")
|
|
@@ -176,10 +183,12 @@ def claim_type(text) -> str:
|
|
|
176
183
|
return A_CLAIM
|
|
177
184
|
|
|
178
185
|
|
|
186
|
+
# 生效条件:给定 text,返回其去除首尾空白后是否以 CONDITION_MARK 开头。
|
|
179
187
|
def is_conditioned(text) -> bool:
|
|
180
188
|
return str(text or "").strip().startswith(CONDITION_MARK)
|
|
181
189
|
|
|
182
190
|
|
|
191
|
+
# 生效条件:给定 text、label、source,若 text 非空且 label 非空且 source 解析后非空,则返回带 CONDITION_MARK 的来源限定表述;已条件化原样返回;否则 None。
|
|
183
192
|
def conditioned_claim(text, label, source):
|
|
184
193
|
"""把评价性断言改写为**带来源限定的条件表述**;缺来源/标签则返回 `None`(不写)。
|
|
185
194
|
|
|
@@ -198,6 +207,7 @@ def conditioned_claim(text, label, source):
|
|
|
198
207
|
return f"{CONDITION_MARK}据{label}({src})的表述:{body}"
|
|
199
208
|
|
|
200
209
|
|
|
210
|
+
# 生效条件:给定 fm 与 content,提取 CCG 声明字段、comment 值与正文长句,返回断言列表,每项含 text/type/where/field。
|
|
201
211
|
def extract_claims(fm: dict, content: str) -> list:
|
|
202
212
|
"""提取可核对断言:CCG 声明字段 + comment 值 + 正文长句。
|
|
203
213
|
|
|
@@ -206,6 +216,7 @@ def extract_claims(fm: dict, content: str) -> list:
|
|
|
206
216
|
"""
|
|
207
217
|
out, seen = [], set()
|
|
208
218
|
|
|
219
|
+
# 生效条件:仅当 str(text or "").strip() 得到的 s 长度 >= 4、s 不在 seen 中、且 nodefile.is_placeholder_text(s) 为假时,把 {text: s, type: claim_type(s), where, field} 追加进 out 并把 s 加入 seen,否则直接返回(field 默认 "")。
|
|
209
220
|
def _push(text, where, field=""):
|
|
210
221
|
s = str(text or "").strip()
|
|
211
222
|
if len(s) < 4 or s in seen or nodefile.is_placeholder_text(s):
|
|
@@ -240,6 +251,7 @@ def extract_claims(fm: dict, content: str) -> list:
|
|
|
240
251
|
return out
|
|
241
252
|
|
|
242
253
|
|
|
254
|
+
# 生效条件:给定 fm、content、claim、new_text,按 claim.where 定位并在唯一匹配时替换断言返回 (content, True),否则返回 (content, False)。
|
|
243
255
|
def _rewrite_claim(fm: dict, content: str, claim: dict, new_text: str):
|
|
244
256
|
"""节点内定位并替换一条断言 → `(content, ok)`;定位不唯一则 fail-closed 不动。"""
|
|
245
257
|
where, field = claim.get("where"), claim.get("field")
|
|
@@ -269,6 +281,7 @@ def _rewrite_claim(fm: dict, content: str, claim: dict, new_text: str):
|
|
|
269
281
|
|
|
270
282
|
# ---- 工单 ----------------------------------------------------------------
|
|
271
283
|
|
|
284
|
+
# 生效条件:给定 fm 与 content,返回缺失项列表:verification_basis 无效则加入该名,正文无 "# 验证方式" 行则加入该名。
|
|
272
285
|
def _need(fm: dict, content: str) -> list:
|
|
273
286
|
need = []
|
|
274
287
|
if not nodefile.verification_basis_valid(fm):
|
|
@@ -278,6 +291,7 @@ def _need(fm: dict, content: str) -> list:
|
|
|
278
291
|
return need
|
|
279
292
|
|
|
280
293
|
|
|
294
|
+
# 生效条件:给定 nid、e、fm、content,返回含 id、layer、track、claims、need、source_policy 的工单行字典。
|
|
281
295
|
def _worklist_row(nid: str, e: dict, fm: dict, content: str) -> dict:
|
|
282
296
|
track = classify_track(fm, content)
|
|
283
297
|
return {
|
|
@@ -290,6 +304,7 @@ def _worklist_row(nid: str, e: dict, fm: dict, content: str) -> dict:
|
|
|
290
304
|
}
|
|
291
305
|
|
|
292
306
|
|
|
307
|
+
# 生效条件:给定 fm 与 content,若正文或 comment 中声明的执行字段为占位文本则返回 True;未声明执行时以正文整体占位判定。
|
|
293
308
|
def _is_placeholder_shell(fm: dict, content: str) -> bool:
|
|
294
309
|
"""空壳判定:核心可执行内容未被填充 → 禁止接线(不得把「待填充」固化成事实)。
|
|
295
310
|
|
|
@@ -305,6 +320,7 @@ def _is_placeholder_shell(fm: dict, content: str) -> bool:
|
|
|
305
320
|
return nodefile.is_placeholder_text(content)
|
|
306
321
|
|
|
307
322
|
|
|
323
|
+
# 生效条件:给定 cg,逐节点按 layer/ids/prefix 过滤后产出状态为 skip(internal/denied/locked/derived/present/placeholder/unreadable 等)或 row 的扫描结果。
|
|
308
324
|
def _scan(cg, layer=None, ids=None, prefix=None):
|
|
309
325
|
"""逐节点产出扫描结果:`{"status", "reason"?, "id", "row"?}`。
|
|
310
326
|
|
|
@@ -355,6 +371,7 @@ _SKIP_KEY = {"locked": "skipped_locked", "derived": "skipped_derived",
|
|
|
355
371
|
"unreadable": "skipped_unreadable", "internal": "skipped_internal"}
|
|
356
372
|
|
|
357
373
|
|
|
374
|
+
# 生效条件:给定 x(路径或 MdCGOS),只读扫描并生成缺 verification_basis 或 "# 验证方式" 的节点工单,返回统计 rep。
|
|
358
375
|
def build_worklist(x, layer=None, limit=None, ids=None, prefix=None) -> dict:
|
|
359
376
|
"""生成核对工单(只读):缺 `verification_basis`/`验证方式` 的节点入列。
|
|
360
377
|
|
|
@@ -418,6 +435,7 @@ _VERIFY_TEMPLATE = """你是独立**验证单元**(verify)。对下列候选
|
|
|
418
435
|
硬约束:你只能否决(drop)或存疑(defer),**不得新增候选、不得改写 value**。"""
|
|
419
436
|
|
|
420
437
|
|
|
438
|
+
# 生效条件:给定 row、fm、content,用 row 的 track/source_policy/need/claims 与 fm 标题、content 前 1200 字符填充反思模板并返回字符串。
|
|
421
439
|
def reflect_prompt(row: dict, fm: dict, content: str) -> str:
|
|
422
440
|
claims = "\n".join(f"- [{c['type']}] {c['text']}" for c in (row.get("claims") or []))
|
|
423
441
|
return _REFLECT_TEMPLATE.format(
|
|
@@ -428,6 +446,7 @@ def reflect_prompt(row: dict, fm: dict, content: str) -> str:
|
|
|
428
446
|
body=(content or "")[:1200])
|
|
429
447
|
|
|
430
448
|
|
|
449
|
+
# 生效条件:给定 row 与 rows,把候选字段、值、依据、来源序列化为 JSON 并填充验证模板返回字符串。
|
|
431
450
|
def verify_prompt(row: dict, rows: list) -> str:
|
|
432
451
|
cands = [{"field": r.get("field"), "value": r.get("value"),
|
|
433
452
|
"basis": r.get("basis"), "source": r.get("source")} for r in rows]
|
|
@@ -436,6 +455,7 @@ def verify_prompt(row: dict, rows: list) -> str:
|
|
|
436
455
|
candidates=json.dumps(cands, ensure_ascii=False))
|
|
437
456
|
|
|
438
457
|
|
|
458
|
+
# 生效条件:raw 经 str(raw or "") 得 s 后,want_list 为真时先试 s 首个 "[" 至末个 "]"、再试首个 "{" 至末个 "}"(want_list 假值时只试花括号),区间可被 json.loads 解析且结果为 list 时原样返回该 list;结果为 dict 时按 rows/items/verdicts/candidates/data 顺序取首个 obj.get(key) 为 list 的 obj[key],都不满足则返回 [obj],非 list/dict 或区间缺失、解析抛 ValueError 时继续下一组括号,全部落空(含 raw 为假值使 s 为空串)返回 []。
|
|
439
459
|
def _extract_json(raw, want_list=True):
|
|
440
460
|
"""从模型输出里抽取 JSON(容忍代码围栏与前后废话)。"""
|
|
441
461
|
s = str(raw or "")
|
|
@@ -458,6 +478,7 @@ def _extract_json(raw, want_list=True):
|
|
|
458
478
|
return []
|
|
459
479
|
|
|
460
480
|
|
|
481
|
+
# 生效条件:给定 item,若为 dict 则规范化 field/value/basis/source/verdict/reason 后返回字典,否则返回 {}。
|
|
461
482
|
def _norm_row(item) -> dict:
|
|
462
483
|
if not isinstance(item, dict):
|
|
463
484
|
return {}
|
|
@@ -471,6 +492,7 @@ def _norm_row(item) -> dict:
|
|
|
471
492
|
}
|
|
472
493
|
|
|
473
494
|
|
|
495
|
+
# 生效条件:给定 raw,解析 JSON 行并保留 field 为“验证方式”或“verification_basis”(统一为“验证方式”)的行,返回列表。
|
|
474
496
|
def parse_reflect_rows(raw) -> list:
|
|
475
497
|
out = []
|
|
476
498
|
for item in _extract_json(raw, want_list=True):
|
|
@@ -482,6 +504,7 @@ def parse_reflect_rows(raw) -> list:
|
|
|
482
504
|
return out
|
|
483
505
|
|
|
484
506
|
|
|
507
|
+
# 生效条件:遍历 _extract_json(raw, want_list=False)(只认花括号 JSON)的结果,仅当 item 经 _norm_row 后为真且 r["field"] 非空时产出 {field,value,verdict,reason} 四键行,否则跳过(raw 无可解析花括号对象时 out 为空列表)。
|
|
485
508
|
def parse_verify_rows(raw) -> list:
|
|
486
509
|
out = []
|
|
487
510
|
for item in _extract_json(raw, want_list=False):
|
|
@@ -494,6 +517,7 @@ def parse_verify_rows(raw) -> list:
|
|
|
494
517
|
|
|
495
518
|
# ---- 白箱闸门与双单元折叠 ------------------------------------------------
|
|
496
519
|
|
|
520
|
+
# 生效条件:逐行处理 rows,仅当 normalize_field(r.get("field")) 落在 WRITABLE_FIELDS、basis_licensed(track, r.get("basis")) 为真、r.get("source") 为真、且 r.get("value") or BASIS_TEXT.get(str(r.get("basis")), "") 非空时进入 kept(附 verdict="accept"),否则该行带对应 reason 进入 gated。
|
|
497
521
|
def gate_rows(rows: list, track: str) -> tuple:
|
|
498
522
|
"""零模型白箱闸门:字段越界 / 来源执照不通过 / 无来源 → 一律降级为 defer。
|
|
499
523
|
|
|
@@ -520,6 +544,7 @@ def gate_rows(rows: list, track: str) -> tuple:
|
|
|
520
544
|
return kept, gated
|
|
521
545
|
|
|
522
546
|
|
|
547
|
+
# 生效条件:按 (r.get("id"), normalize_field(r.get("field")) or r.get("field"), r.get("value")) 分组后,组内缺 unit==REFLECT_UNIT 或 unit==VERIFY_UNIT 的行时进 deferred,否则 verify 侧出现 verdict=="drop" 即进 dropped(veto 优先),再否则仅当 reflect 与 verify 各存在 verdict=="accept" 时才进 accepted(附 units),其余进 deferred。
|
|
523
548
|
def fold_verdicts(rows: list) -> tuple:
|
|
524
549
|
"""把两单元裁决折叠为可落库结论 → `(accepted, deferred, dropped)`。
|
|
525
550
|
|
|
@@ -562,6 +587,7 @@ def fold_verdicts(rows: list) -> tuple:
|
|
|
562
587
|
return accepted, deferred, dropped
|
|
563
588
|
|
|
564
589
|
|
|
590
|
+
# 生效条件:当 rows 中 unit==REFLECT_UNIT 与 unit==VERIFY_UNIT 的执行者经 str(x.get("actor") or "") 后存在相同的非空值(空串被 discard)时返回 True,否则返回 False。
|
|
565
591
|
def detect_self_verify(rows: list) -> bool:
|
|
566
592
|
"""同一执行者同时充当反思与验证 = 自证(禁止)。"""
|
|
567
593
|
r = {str(x.get("actor") or "") for x in rows if x.get("unit") == REFLECT_UNIT}
|
|
@@ -573,6 +599,7 @@ def detect_self_verify(rows: list) -> bool:
|
|
|
573
599
|
|
|
574
600
|
# ---- 落库写入 ------------------------------------------------------------
|
|
575
601
|
|
|
602
|
+
# 生效条件:verdicts 为 None 时返回 None;verdicts 为 dict 时对每个键值把 (rs or []) 中的 dict 元素收为 {str(nid): [...]};否则遍历 verdicts or [],仅当元素为 dict 且 str(r.get("id") or "") 非空时按该 id 追加到对应列表。
|
|
576
603
|
def _norm_verdicts(verdicts):
|
|
577
604
|
"""外部裁决(子代理落盘)→ `{id: [rows]}`。"""
|
|
578
605
|
if verdicts is None:
|
|
@@ -591,6 +618,7 @@ def _norm_verdicts(verdicts):
|
|
|
591
618
|
return out
|
|
592
619
|
|
|
593
620
|
|
|
621
|
+
# 生效条件:accepted 非空(取 accepted[0])时,先以 basis=str(a.get("basis") or BASIS_ENUM_DEFAULT) 与 value=str(a.get("value") or BASIS_TEXT.get(basis, "")).strip() 写「验证方式」行与 comment,之后才在 nodefile.verification_basis_valid(fm) 为真时把 basis 换成 fm.get("verification_basis")、否则把该 basis 写入 fm["verification_basis"];condition_claims 为真时仅对 row.get("claims") 中 type==B_CLAIM 且未被 is_conditioned 的条目做条件化改写,返回含 fm_before、content_hash_before 等留痕的 dict。
|
|
594
622
|
def _apply_node(cg, nid, e, fm, content, accepted, row, batch, actor,
|
|
595
623
|
condition_claims=True):
|
|
596
624
|
"""把一个节点的已接受结论写入 md,返回留痕记录(含回滚所需现场)。"""
|
|
@@ -664,6 +692,7 @@ def _apply_node(cg, nid, e, fm, content, accepted, row, batch, actor,
|
|
|
664
692
|
|
|
665
693
|
# ---- 主流程 --------------------------------------------------------------
|
|
666
694
|
|
|
695
|
+
# 生效条件:reflect_fn(reflect_prompt(scan_row, fm, content)) 经 parse_reflect_rows 得到非空候选时返回 (rrows, vrows),rrows 为空则返回 ([], []);verify_fn 为 None 时 vrows 为空列表,非 None 时由 parse_verify_rows(verify_fn(verify_prompt(scan_row, rrows))) 生成、每行 value 为 c.get("value") or rrows 中同 field 的 value、再回落 ""。
|
|
667
696
|
def _rows_for(scan_row, fm, content, reflect_fn, verify_fn, r_actor, v_actor):
|
|
668
697
|
"""调用两单元子代理,返回合并后的裁决行(reflect + verify)。"""
|
|
669
698
|
prompt = reflect_prompt(scan_row, fm, content)
|
|
@@ -684,6 +713,7 @@ def _rows_for(scan_row, fm, content, reflect_fn, verify_fn, r_actor, v_actor):
|
|
|
684
713
|
return rrows, vrows
|
|
685
714
|
|
|
686
715
|
|
|
716
|
+
# 生效条件:对 pre 中每个 r,str(r.get("unit") or REFLECT_UNIT).strip().lower() 等于 VERIFY_UNIT 时进 vrows,否则(含 unit 缺失回落到 REFLECT_UNIT 及任何其他取值)进 rrows,两组行均覆盖 id=nid、unit、track=track。
|
|
687
717
|
def _rows_from_verdicts(nid, track, pre):
|
|
688
718
|
"""从外部裁决中拆出 (reflect, verify) 两组行。"""
|
|
689
719
|
rrows, vrows = [], []
|
|
@@ -694,6 +724,7 @@ def _rows_from_verdicts(nid, track, pre):
|
|
|
694
724
|
return rrows, vrows
|
|
695
725
|
|
|
696
726
|
|
|
727
|
+
# 生效条件:x 经 _as_cg 解析且 batch = batch or CROSSCHECK_BATCH 后逐节点扫描,裁决来源按 vmap(verdicts 归一化后非 None)→ reflect_fn 非 None → 二者皆无记 no_reflect 三条分支取行;allow_self_verify=False 时同执行者自证记 self_verify_disallowed,再经 gate_rows 闸门与 require_verify 后 fold_verdicts,仅 apply=True 才 _apply_node 写盘并在有写入时 cg.rebuild_index;limit 非 None 且已达标数 >= limit 时用 continue 跳过(非终止)。
|
|
697
728
|
def crosscheck(x, layer=None, limit=None, ids=None, reflect_fn=None,
|
|
698
729
|
verify_fn=None, verdicts=None, apply=False,
|
|
699
730
|
batch=CROSSCHECK_BATCH, actor=None, require_verify=True,
|
|
@@ -721,9 +752,11 @@ def crosscheck(x, layer=None, limit=None, ids=None, reflect_fn=None,
|
|
|
721
752
|
"placeholder_ids": [], "undetermined": 0,
|
|
722
753
|
"reasons": {}, "samples": [], "entry_ids": []}
|
|
723
754
|
|
|
755
|
+
# 生效条件:无条件执行 rep["reasons"][reason] = rep["reasons"].get(reason, 0) + 1(reason 缺键时按 .get 的第二参数 0 起算),返回 None。
|
|
724
756
|
def _bump(reason):
|
|
725
757
|
rep["reasons"][reason] = rep["reasons"].get(reason, 0) + 1
|
|
726
758
|
|
|
759
|
+
# 生效条件:仅当外层 verbose 为真且 len(rep["samples"]) < 20 时把 {kind, id: nid, detail} 追加进 rep["samples"],否则不追加(已达 20 条即停止采样)。
|
|
727
760
|
def _sample(kind, nid, detail=""):
|
|
728
761
|
if verbose and len(rep["samples"]) < 20:
|
|
729
762
|
rep["samples"].append({"kind": kind, "id": nid, "detail": detail})
|
|
@@ -828,10 +861,12 @@ def crosscheck(x, layer=None, limit=None, ids=None, reflect_fn=None,
|
|
|
828
861
|
|
|
829
862
|
# ---- 留痕查询 / 回滚 -----------------------------------------------------
|
|
830
863
|
|
|
864
|
+
# 生效条件:无条件返回 os.path.join(cg.root, CROSSCHECK_LOG)(以 cg.root 与常量 CROSSCHECK_LOG 拼接,无分支)。
|
|
831
865
|
def _log_path(cg) -> str:
|
|
832
866
|
return os.path.join(cg.root, CROSSCHECK_LOG)
|
|
833
867
|
|
|
834
868
|
|
|
869
|
+
# 生效条件:box 非 dict 时返回 False;box 为 dict 且 key=="comment_verification" 时按 box.get("had") 为真则把 comment 的「验证方式」设为 box.get("value")、否则删除该键并返回 True;其他 key 时 had 为真赋 fm[key]=value、否则 fm.pop(key, None) 并返回 True。
|
|
835
870
|
def _reattach(fm: dict, content: str, box: dict, key: str):
|
|
836
871
|
"""把 `fm_before[key]` 现场还原到 fm,返回是否发生还原。"""
|
|
837
872
|
if not isinstance(box, dict):
|
|
@@ -851,6 +886,7 @@ def _reattach(fm: dict, content: str, box: dict, key: str):
|
|
|
851
886
|
return True
|
|
852
887
|
|
|
853
888
|
|
|
889
|
+
# 生效条件:仅当 str(c.get("after") or "") 非空,且分别满足 where=="ccg" 且 field 真值且 _ccg_field(content, field).strip()==after.strip()(用 before 覆盖该行)、where=="comment" 且 field 真值且 comment 该 field 为含 after 的 list 或 str(v or "").strip()==after.strip()(改为 before)、where=="body" 且 after 出现在 content 中(替换首个匹配)时返回 (content, True);其余情形(含 where 为其他值、字段缺失、当前值不等于写入值)返回 (content, False)。
|
|
854
890
|
def _rewind_claim(fm: dict, content: str, c: dict):
|
|
855
891
|
"""撤销一条条件化改写(仅当前值 == 写入值时才动)→ `(content, ok)`。"""
|
|
856
892
|
where, field = c.get("where"), c.get("field")
|
|
@@ -880,6 +916,7 @@ def _rewind_claim(fm: dict, content: str, c: dict):
|
|
|
880
916
|
return content, False
|
|
881
917
|
|
|
882
918
|
|
|
919
|
+
# 生效条件:x 经 _as_cg 后,对 read_jsonl(_log_path(cg)) 中 action=="crosscheck"、batch 为 None 或等于参数 batch、且 entry_ids 为假值不做 id 过滤(为真值时仅取 entry_id 在集合中的)的记录逐条处理:node 缺失或已处理则跳过,索引无该 node 或 cg._read 得 fm 为 None 或 crypto.is_encrypted(content) 为真时 skipped_drift 加一,write_id 双方非空且不等时 conflict 加一,否则撤销 claims_conditioned、在当前「验证方式」行非空且等于 rec 的 verification_value 时撤销该行、再按 fm_before 还原,reverted 为空则 conflict 加一,非空则写回节点、追加 crosscheck_rollback 日志、reverted 与 entry_ids 加一,最终 reverted 非零时 cg.rebuild_index(),返回 rep;
|
|
883
920
|
def rollback(x, batch=None, entry_ids=None, actor=None) -> dict:
|
|
884
921
|
"""按留痕反向应用:撤销核对写入(当前值 ≠ 写入值时跳过,计入 conflict)。"""
|
|
885
922
|
cg = _as_cg(x)
|
|
@@ -945,6 +982,7 @@ def rollback(x, batch=None, entry_ids=None, actor=None) -> dict:
|
|
|
945
982
|
return rep
|
|
946
983
|
|
|
947
984
|
|
|
985
|
+
# 生效条件:遍历 _log_path(cg) 的记录时,action 为真值只留 rec.get("action")==action 的行、batch 为真值只留 rec.get("batch")==batch 的行;limit 非 None 且 limit>=0 时按 recs[-limit:] 截取(limit 为 0 时 [-0:] 即整表不被削减),否则保留全部;返回 {'root','total','returned','records'}。
|
|
948
986
|
def history(x, limit=100, action=None, batch=None) -> dict:
|
|
949
987
|
cg = _as_cg(x)
|
|
950
988
|
recs = []
|
|
@@ -963,6 +1001,7 @@ def history(x, limit=100, action=None, batch=None) -> dict:
|
|
|
963
1001
|
|
|
964
1002
|
# ---- 权限与 CLI ----------------------------------------------------------
|
|
965
1003
|
|
|
1004
|
+
# 生效条件:principal 为 None 时返回 False;否则仅当 principal.expired() 为假、principal.can_write 为真、且 principal.allows_layer("knowledge") 为真时返回 True,期间任一步抛 Exception 亦返回 False。
|
|
966
1005
|
def can_write_knowledge(principal) -> bool:
|
|
967
1006
|
"""落 knowledge 层必须持有可写该层的令牌(designer 派生);否则 fail-closed。"""
|
|
968
1007
|
if principal is None:
|
|
@@ -975,6 +1014,7 @@ def can_write_knowledge(principal) -> bool:
|
|
|
975
1014
|
return False
|
|
976
1015
|
|
|
977
1016
|
|
|
1017
|
+
# 生效条件:path 为假值(空串/None)返回 None;path 不存在则 raise SystemExit;已存在且读取文本 strip 后为空串返回 [],非空时整段 json.loads 成功即返回该值,抛 ValueError 时按行解析(跳过空行与 "//" 开头行)返回行列表。
|
|
978
1018
|
def _load_verdicts(path: str):
|
|
979
1019
|
if not path:
|
|
980
1020
|
return None
|
|
@@ -996,6 +1036,7 @@ def _load_verdicts(path: str):
|
|
|
996
1036
|
return rows
|
|
997
1037
|
|
|
998
1038
|
|
|
1039
|
+
# 生效条件:argv(为 None 时由 argparse 读 sys.argv)解析后按 --action 分派——worklist 调 build_worklist,history 调 history(--limit 默认 None,为 None 时传 100),rollback 在 can_write_knowledge(principal) 为假时抛 SystemExit 否则调 rollback,crosscheck 在 --apply 为真且 can_write_knowledge(principal) 为假时抛 SystemExit 否则调 crosscheck;--token(默认 os.environ.get("MDCG_TOKEN") or "")为真值时先 tokens.verify_token 校验、失败抛 SystemExit;最后打印 rep 并返回 0;
|
|
999
1040
|
def _cli(argv=None) -> int:
|
|
1000
1041
|
ap = argparse.ArgumentParser(
|
|
1001
1042
|
prog="python -m md_cg.crosscheck",
|
|
@@ -1054,5 +1095,4 @@ def _cli(argv=None) -> int:
|
|
|
1054
1095
|
|
|
1055
1096
|
|
|
1056
1097
|
if __name__ == "__main__": # pragma: no cover
|
|
1057
|
-
sys.exit(_cli())
|
|
1058
|
-
|
|
1098
|
+
sys.exit(_cli())
|
package/md_cg/crypto.py
CHANGED
|
@@ -68,20 +68,24 @@ MASTER_FILE = os.path.join(os.path.expanduser("~"), ".mdcg", "master.key")
|
|
|
68
68
|
SCRYPT_N, SCRYPT_R, SCRYPT_P, SCRYPT_DKLEN = 2 ** 14, 8, 1, 32
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
# 生效条件:不适用(无必需形参与模块级常量)
|
|
71
72
|
class CryptoError(Exception):
|
|
72
73
|
"""加解密 / 密钥相关错误。"""
|
|
73
74
|
|
|
74
75
|
|
|
76
|
+
# 生效条件:不适用(无必需形参与模块级常量)
|
|
75
77
|
class LockedError(CryptoError):
|
|
76
78
|
"""无密钥或身份不符——内容不可读(fail-closed,绝不降级为明文)。"""
|
|
77
79
|
|
|
78
80
|
|
|
79
81
|
# ---- ChaCha20(RFC 8439 §2.3)--------------------------------------------
|
|
80
82
|
|
|
83
|
+
# 生效条件:x、n 为入参,返回 ((x << n) & 0xFFFFFFFF) | (x >> (32 - n));源码未校验 x、n 类型或范围。
|
|
81
84
|
def _rotl32(x, n):
|
|
82
85
|
return ((x << n) & 0xFFFFFFFF) | (x >> (32 - n))
|
|
83
86
|
|
|
84
87
|
|
|
88
|
+
# 生效条件:s、a、b、c、d 为入参,依次读写 s[a]、s[b]、s[c]、s[d] 并按源码顺序做加法、异或、_rotl32 更新;源码未校验 s 元素类型或索引范围。
|
|
85
89
|
def _quarter_round(s, a, b, c, d):
|
|
86
90
|
s[a] = (s[a] + s[b]) & 0xFFFFFFFF
|
|
87
91
|
s[d] = _rotl32(s[d] ^ s[a], 16)
|
|
@@ -93,6 +97,7 @@ def _quarter_round(s, a, b, c, d):
|
|
|
93
97
|
s[b] = _rotl32(s[b] ^ s[c], 7)
|
|
94
98
|
|
|
95
99
|
|
|
100
|
+
# 生效条件:key、counter、nonce 为入参,按 const + key 解包 + counter 低 32 位 + nonce 解包构造 state,执行 10 次双轮后返回 16 个 32 位小端打包的 64 字节块。
|
|
96
101
|
def _chacha_block(key, counter, nonce):
|
|
97
102
|
"""生成 64 字节密钥流块。"""
|
|
98
103
|
const = b"expand 32-byte k"
|
|
@@ -113,6 +118,7 @@ def _chacha_block(key, counter, nonce):
|
|
|
113
118
|
return struct.pack("<16I", *[(w[i] + st[i]) & 0xFFFFFFFF for i in range(16)])
|
|
114
119
|
|
|
115
120
|
|
|
121
|
+
# 生效条件:key、counter、nonce、data 为入参,对 data 从 0 到 len(data) 步长 64 分块,每块用 _chacha_block(key, counter + i//64, nonce) 生成密钥流并逐字节异或,返回等长 bytes;data 为空时返回 b""。
|
|
116
122
|
def _chacha20_xor(key, counter, nonce, data):
|
|
117
123
|
out = bytearray(len(data))
|
|
118
124
|
for i in range(0, len(data), 64):
|
|
@@ -124,6 +130,7 @@ def _chacha20_xor(key, counter, nonce, data):
|
|
|
124
130
|
|
|
125
131
|
# ---- Poly1305(RFC 8439 §2.5)-------------------------------------------
|
|
126
132
|
|
|
133
|
+
# 生效条件:key、msg 为入参,取 key[:16] 掩码得 r、key[16:] 得 s,msg 按 16 字节分块加 b"\x01" 累加,返回 (acc + s) 低 128 位的 16 字节小端;源码未校验 key 长度或 msg 类型。
|
|
127
134
|
def _poly1305(key, msg):
|
|
128
135
|
r = int.from_bytes(key[:16], "little") & 0x0FFFFFFC0FFFFFFC0FFFFFFC0FFFFFFF
|
|
129
136
|
s = int.from_bytes(key[16:], "little")
|
|
@@ -135,10 +142,12 @@ def _poly1305(key, msg):
|
|
|
135
142
|
return ((acc + s) & ((1 << 128) - 1)).to_bytes(16, "little")
|
|
136
143
|
|
|
137
144
|
|
|
145
|
+
# 生效条件:b 为入参,返回 b"\x00" * ((16 - len(b) % 16) % 16);b 长度为 16 的倍数(含空)时返回 b""。
|
|
138
146
|
def _pad16(b):
|
|
139
147
|
return b"\x00" * ((16 - len(b) % 16) % 16)
|
|
140
148
|
|
|
141
149
|
|
|
150
|
+
# 生效条件:otk、aad、ct 为入参,返回 _poly1305(otk, aad + _pad16(aad) + ct + _pad16(ct) + struct.pack("<Q", len(aad)) + struct.pack("<Q", len(ct))) 的结果。
|
|
142
151
|
def _aead_mac(otk, aad, ct):
|
|
143
152
|
return _poly1305(otk, aad + _pad16(aad) + ct + _pad16(ct)
|
|
144
153
|
+ struct.pack("<Q", len(aad)) + struct.pack("<Q", len(ct)))
|
|
@@ -146,6 +155,7 @@ def _aead_mac(otk, aad, ct):
|
|
|
146
155
|
|
|
147
156
|
# ---- AEAD:ChaCha20-Poly1305(RFC 8439 §2.8)-----------------------------
|
|
148
157
|
|
|
158
|
+
# 生效条件:key、nonce、plaintext、aad=b"" 为入参;len(key) != KEY_LEN 或 len(nonce) != NONCE_LEN 时抛 CryptoError;否则以 _chacha_block(key,0,nonce)[:32] 为 otk、_chacha20_xor(key,1,nonce,plaintext) 为 ct,返回 (ct, _aead_mac(otk, aad, ct))。
|
|
149
159
|
def aead_encrypt(key, nonce, plaintext, aad=b""):
|
|
150
160
|
"""返回 (ciphertext, tag)。key=32B / nonce=12B。"""
|
|
151
161
|
if len(key) != KEY_LEN:
|
|
@@ -157,6 +167,7 @@ def aead_encrypt(key, nonce, plaintext, aad=b""):
|
|
|
157
167
|
return ct, _aead_mac(otk, aad, ct)
|
|
158
168
|
|
|
159
169
|
|
|
170
|
+
# 生效条件:key、nonce、ct、tag、aad=b"" 为入参;len(key) != KEY_LEN 或 len(nonce) != NONCE_LEN 时抛 CryptoError;否则算 otk,若 hmac.compare_digest(_aead_mac(otk,aad,ct), tag) 为假抛 CryptoError,为真返回 _chacha20_xor(key,1,nonce,ct)。
|
|
160
171
|
def aead_decrypt(key, nonce, ct, tag, aad=b""):
|
|
161
172
|
"""验签后解密;失败抛 CryptoError(不返回任何明文)。"""
|
|
162
173
|
if len(key) != KEY_LEN:
|
|
@@ -171,20 +182,24 @@ def aead_decrypt(key, nonce, ct, tag, aad=b""):
|
|
|
171
182
|
|
|
172
183
|
# ---- KDF / 主密钥(KEK)--------------------------------------------------
|
|
173
184
|
|
|
185
|
+
# 生效条件:b 为入参,返回 base64.b64encode(b).decode("ascii") 得到的字符串。
|
|
174
186
|
def _b64e(b):
|
|
175
187
|
return base64.b64encode(b).decode("ascii")
|
|
176
188
|
|
|
177
189
|
|
|
190
|
+
# 生效条件:s 为入参,返回 base64.b64decode(str(s).encode("ascii")) 的结果;默认 validate=False,非字母字符被丢弃,可能返回空字节或部分数据,源码未校验 s 合法性。
|
|
178
191
|
def _b64d(s):
|
|
179
192
|
return base64.b64decode(str(s).encode("ascii"))
|
|
180
193
|
|
|
181
194
|
|
|
195
|
+
# 生效条件:passphrase、salt 为入参,将 str(passphrase).encode("utf-8") 作为口令、salt 作为盐,按 SCRYPT_N、SCRYPT_R、SCRYPT_P、SCRYPT_DKLEN 调 hashlib.scrypt 返回 KEK。
|
|
182
196
|
def scrypt_kek(passphrase, salt):
|
|
183
197
|
"""口令 → KEK(scrypt)。用于「人类口令」场景,避免直接存放原始密钥。"""
|
|
184
198
|
return hashlib.scrypt(str(passphrase).encode("utf-8"), salt=salt,
|
|
185
199
|
n=SCRYPT_N, r=SCRYPT_R, p=SCRYPT_P, dklen=SCRYPT_DKLEN)
|
|
186
200
|
|
|
187
201
|
|
|
202
|
+
# 生效条件:master_file=None、env_var=MASTER_ENV、create=True 为可选入参;从 os.environ.get(env_var) 读取并 strip,若 raw 非空则长度 64 走 bytes.fromhex、否则 _b64d,解码失败抛 CryptoError;否则 path = master_file or MASTER_FILE,若 os.path.exists(path) 为真则读取并 _b64d;若 create 为假返回 None;否则生成 KEY_LEN 随机密钥写入 path 并返回 key。
|
|
188
203
|
def load_master_key(master_file=None, env_var=MASTER_ENV, create=True):
|
|
189
204
|
"""KEK 来源:环境变量 → 主密钥文件(可选自动生成);都没有返回 None。"""
|
|
190
205
|
raw = (os.environ.get(env_var) or "").strip()
|
|
@@ -210,22 +225,26 @@ def load_master_key(master_file=None, env_var=MASTER_ENV, create=True):
|
|
|
210
225
|
return key
|
|
211
226
|
|
|
212
227
|
|
|
228
|
+
# 生效条件:kek 为入参,返回 hashlib.sha256(b"mdcg-kek|" + kek).hexdigest()[:16];源码未校验 kek 类型。
|
|
213
229
|
def kek_fingerprint(kek):
|
|
214
230
|
return hashlib.sha256(b"mdcg-kek|" + kek).hexdigest()[:16]
|
|
215
231
|
|
|
216
232
|
|
|
217
233
|
# ---- 身份一致性(AAD 绑定)----------------------------------------------
|
|
218
234
|
|
|
235
|
+
# 生效条件:tenant、actor 为入参,返回 hashlib.sha256(f"mdcg-id|v{ENVELOPE_VERSION}|{tenant}|{actor}".encode("utf-8")).hexdigest()[:16];ENVELOPE_VERSION 为模块级常量。
|
|
219
236
|
def identity_fingerprint(tenant, actor):
|
|
220
237
|
"""身份指纹:tenant + actor 的确定性摘要(不泄露原文)。"""
|
|
221
238
|
raw = f"mdcg-id|v{ENVELOPE_VERSION}|{tenant}|{actor}".encode("utf-8")
|
|
222
239
|
return hashlib.sha256(raw).hexdigest()[:16]
|
|
223
240
|
|
|
224
241
|
|
|
242
|
+
# 生效条件:tenant、actor 为入参,返回 f"mdcg-dek|v{ENVELOPE_VERSION}|{tenant}|{actor}".encode("utf-8")。
|
|
225
243
|
def _dek_aad(tenant, actor):
|
|
226
244
|
return f"mdcg-dek|v{ENVELOPE_VERSION}|{tenant}|{actor}".encode("utf-8")
|
|
227
245
|
|
|
228
246
|
|
|
247
|
+
# 生效条件:node_id、tenant、actor 为入参,返回 f"mdcg-node|v{ENVELOPE_VERSION}|{tenant}|{actor}|{node_id}".encode("utf-8")。
|
|
229
248
|
def _node_aad(node_id, tenant, actor):
|
|
230
249
|
return (f"mdcg-node|v{ENVELOPE_VERSION}|{tenant}|{actor}|{node_id}"
|
|
231
250
|
.encode("utf-8"))
|
|
@@ -233,10 +252,12 @@ def _node_aad(node_id, tenant, actor):
|
|
|
233
252
|
|
|
234
253
|
# ---- 密钥库(DEK 信封)---------------------------------------------------
|
|
235
254
|
|
|
255
|
+
# 生效条件:root 为入参,返回 os.path.join(root, KEYS_FILE);KEYS_FILE 为模块级常量。
|
|
236
256
|
def keys_path(root):
|
|
237
257
|
return os.path.join(root, KEYS_FILE)
|
|
238
258
|
|
|
239
259
|
|
|
260
|
+
# 生效条件:root 为入参;若 os.path.exists(keys_path(root)) 为假,返回 {"v": ENVELOPE_VERSION, "alg": ALG, "envelopes": {}};否则尝试 json.load,若结果为 dict 且其 "envelopes" 为 dict 则返回该 dict;若 json 解析 ValueError 或 OSError 则返回同样的空结构。
|
|
240
261
|
def _load_keys(root):
|
|
241
262
|
p = keys_path(root)
|
|
242
263
|
if not os.path.exists(p):
|
|
@@ -251,16 +272,19 @@ def _load_keys(root):
|
|
|
251
272
|
return {"v": ENVELOPE_VERSION, "alg": ALG, "envelopes": {}}
|
|
252
273
|
|
|
253
274
|
|
|
275
|
+
# 生效条件:root、data 为入参,将 data 经 json.dumps(data, ensure_ascii=False, indent=1) 后由 atomic_write 写入 keys_path(root)。
|
|
254
276
|
def _save_keys(root, data):
|
|
255
277
|
from .fsutil import atomic_write
|
|
256
278
|
atomic_write(keys_path(root),
|
|
257
279
|
json.dumps(data, ensure_ascii=False, indent=1))
|
|
258
280
|
|
|
259
281
|
|
|
282
|
+
# 生效条件:tenant、actor 为入参,返回 f"{tenant}|{actor}"。
|
|
260
283
|
def _envelope_key(tenant, actor):
|
|
261
284
|
return f"{tenant}|{actor}"
|
|
262
285
|
|
|
263
286
|
|
|
287
|
+
# 生效条件:root、kek、tenant、actor、clearance="private"、rotate=False 为入参;若 kek 为假抛 LockedError;否则加载 keys,若 _envelope_key(tenant, actor) 已在 envelopes 中且 rotate 为假则返回 unwrap_dek(root, kek, tenant, actor, clearance);否则生成新 DEK 与 nonce,用 aead_encrypt(kek, nonce, dek, _dek_aad(tenant, actor)) 包裹后写入 envelopes[k] 并保存,返回 dek。
|
|
264
288
|
def provision_dek(root, kek, tenant, actor, clearance="private", rotate=False):
|
|
265
289
|
"""为 (tenant, actor) 生成 / 取回 DEK,用 KEK 包裹后存入 `_keys.json`。
|
|
266
290
|
|
|
@@ -288,6 +312,7 @@ def provision_dek(root, kek, tenant, actor, clearance="private", rotate=False):
|
|
|
288
312
|
return dek
|
|
289
313
|
|
|
290
314
|
|
|
315
|
+
# 生效条件:root、kek、tenant、actor、clearance=None 为入参;若 kek 为假抛 LockedError;否则取 _load_keys(root)["envelopes"] 中 _envelope_key(tenant, actor) 的信封,无则抛 LockedError;若 env["id_fp"] 不等于 identity_fingerprint(tenant, actor) 抛 LockedError;否则从 env["ct"] 解出 raw,按 TAG_LEN 切出 ct/tag,调 aead_decrypt;若 aead_decrypt 抛 CryptoError 则转抛 LockedError;clearance 形参默认 None 但源码未在条件中使用。
|
|
291
316
|
def unwrap_dek(root, kek, tenant, actor, clearance=None):
|
|
292
317
|
"""解出 DEK;身份指纹不符 / KEK 不对 / 无信封 → LockedError。"""
|
|
293
318
|
if not kek:
|
|
@@ -306,16 +331,19 @@ def unwrap_dek(root, kek, tenant, actor, clearance=None):
|
|
|
306
331
|
raise LockedError(f"身份 / 主密钥不匹配:{e}") from e
|
|
307
332
|
|
|
308
333
|
|
|
334
|
+
# 生效条件:root、tenant、actor 为入参,返回 _envelope_key(tenant, actor) in (_load_keys(root).get("envelopes") or {}) 的布尔结果。
|
|
309
335
|
def has_envelope(root, tenant, actor):
|
|
310
336
|
return _envelope_key(tenant, actor) in (
|
|
311
337
|
_load_keys(root).get("envelopes") or {})
|
|
312
338
|
|
|
313
339
|
|
|
340
|
+
# 生效条件:root、kek、tenant、actor、clearance="private" 为入参,返回 provision_dek(root, kek, tenant, actor, clearance=clearance, rotate=True)。
|
|
314
341
|
def rotate_dek(root, kek, tenant, actor, clearance="private"):
|
|
315
342
|
return provision_dek(root, kek, tenant, actor, clearance=clearance,
|
|
316
343
|
rotate=True)
|
|
317
344
|
|
|
318
345
|
|
|
346
|
+
# 生效条件:root 为入参,遍历 _load_keys(root).get("envelopes") or {} 的每项,按 "|" partition 出 tenant/actor,收集 id_fp、clearance、created_at 后返回列表;无信封时返回 []。
|
|
319
347
|
def envelopes(root):
|
|
320
348
|
"""信封清单(不含密钥材料):供运维审计「谁被签发了密钥」。"""
|
|
321
349
|
out = []
|
|
@@ -329,10 +357,12 @@ def envelopes(root):
|
|
|
329
357
|
|
|
330
358
|
# ---- 节点正文封装 --------------------------------------------------------
|
|
331
359
|
|
|
360
|
+
# 生效条件:content 为入参;bool(content) 为假(如空串或 None)时返回 False;否则返回 content.lstrip().startswith(ENC_PREFIX)。
|
|
332
361
|
def is_encrypted(content):
|
|
333
362
|
return bool(content) and content.lstrip().startswith(ENC_PREFIX)
|
|
334
363
|
|
|
335
364
|
|
|
365
|
+
# 生效条件:content、dek、node_id、tenant、actor 为入参,生成 NONCE_LEN 随机 nonce,将 str(content).encode("utf-8") 以 aead_encrypt(dek, nonce, ..., _node_aad(node_id, tenant, actor)) 加密,返回 f"{ENC_PREFIX}{_b64e(nonce + tag + ct)}{ENC_SUFFIX}"。
|
|
336
366
|
def seal_node(content, dek, node_id, tenant, actor):
|
|
337
367
|
"""明文 → 密文标记块(正文整体加密;frontmatter 不在此处处理)。"""
|
|
338
368
|
nonce = secrets.token_bytes(NONCE_LEN)
|
|
@@ -341,6 +371,7 @@ def seal_node(content, dek, node_id, tenant, actor):
|
|
|
341
371
|
return f"{ENC_PREFIX}{_b64e(nonce + tag + ct)}{ENC_SUFFIX}"
|
|
342
372
|
|
|
343
373
|
|
|
374
|
+
# 生效条件:content、dek、node_id、tenant、actor 为入参;若 is_encrypted(content) 为假则原样返回 content;否则 strip 后切掉 ENC_PREFIX/ENC_SUFFIX,base64 解出 raw,按 NONCE_LEN、TAG_LEN 切出 nonce/tag/ct,调 aead_decrypt(dek, nonce, ct, tag, _node_aad(node_id, tenant, actor)) 并 utf-8 解码返回;aead_decrypt 失败抛 CryptoError。
|
|
344
375
|
def open_node(content, dek, node_id, tenant, actor):
|
|
345
376
|
"""密文标记块 → 明文;未加密原样返回;失败抛 CryptoError。"""
|
|
346
377
|
if not is_encrypted(content):
|
|
@@ -357,6 +388,7 @@ def open_node(content, dek, node_id, tenant, actor):
|
|
|
357
388
|
|
|
358
389
|
# ---- 审计(payload-free)-------------------------------------------------
|
|
359
390
|
|
|
391
|
+
# 生效条件:root、rec 为入参,复制 rec,若未提供 ts 则设 time.time(),设 payload_free=True,尝试 append_jsonl(os.path.join(root, AUDIT_FILE), rec);OSError 时静默忽略。
|
|
360
392
|
def audit(root, rec):
|
|
361
393
|
from .fsutil import append_jsonl
|
|
362
394
|
rec = dict(rec)
|
|
@@ -368,6 +400,7 @@ def audit(root, rec):
|
|
|
368
400
|
pass
|
|
369
401
|
|
|
370
402
|
|
|
403
|
+
# 生效条件:root 为入参,返回 list(read_jsonl(os.path.join(root, AUDIT_FILE))) 得到的记录列表。
|
|
371
404
|
def audit_records(root):
|
|
372
405
|
from .fsutil import read_jsonl
|
|
373
406
|
return list(read_jsonl(os.path.join(root, AUDIT_FILE)))
|
|
@@ -375,6 +408,7 @@ def audit_records(root):
|
|
|
375
408
|
|
|
376
409
|
# ---- 自描述 --------------------------------------------------------------
|
|
377
410
|
|
|
411
|
+
# 生效条件:无入参,返回包含 module="crypto"、ALG、ENCRYPTED_LEVELS、MASTER_ENV、KEYS_FILE 等模块级常量的自描述字典。
|
|
378
412
|
def catalog():
|
|
379
413
|
"""自描述:加密范围、密钥层级、身份一致性、威胁模型(供 MCP 对照)。"""
|
|
380
414
|
return {
|
|
@@ -401,4 +435,4 @@ def catalog():
|
|
|
401
435
|
"not_covered": ["本地内存取证", "侧信道(纯 Python 实现的固有限制)"],
|
|
402
436
|
},
|
|
403
437
|
"fail_closed": "无 KEK 时拒绝写入 private/secret,不静默降级为明文",
|
|
404
|
-
}
|
|
438
|
+
}
|