@furongjun1999/dsh-memory 0.4.8 → 0.4.10
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 +54 -26
- package/codebuddy/CODEBUDDY.md +196 -195
- 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/README.md +1 -1
- package/docs/discipline/harnesses.yaml +18 -7
- package/docs/discipline/templates/full.md.tmpl +4 -3
- 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/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/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/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.5.md +631 -625
- 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 +47 -45
- 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.4.md → 206_v0.5.md} +92 -4
- 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//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/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 +2 -2
- 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 +3 -3
- 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 +2 -2
- 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 -433
- 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/dsh/README.md +33 -0
- 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 +3 -0
- package/lib/hooks.js +17 -23
- 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 +40 -3
- package/lib/lib/mdcg_client.js +46 -22
- package/lib/lib/mutual.js +4 -4
- package/lib/lib/token_store.js +4 -5
- package/md_cg/audit.py +17 -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 +27 -1
- package/md_cg/bench_p0.py +4 -1
- package/md_cg/bench_progressive.py +13 -1
- 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 +12 -1
- package/md_cg/build_postings.py +73 -0
- package/md_cg/ccgc.py +67 -2
- 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 +69 -4
- package/md_cg/consistency.py +24 -1
- package/md_cg/consolidate.py +53 -2
- package/md_cg/corpus.py +4 -0
- 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 +29 -1
- 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 +23 -1
- package/md_cg/fsutil.py +18 -1
- package/md_cg/hotcache.py +214 -0
- package/md_cg/hyperedge.py +251 -0
- package/md_cg/identity.py +18 -1
- package/md_cg/insight.py +17 -1
- 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 +12 -1
- package/md_cg/linkref.py +281 -0
- package/md_cg/links.py +29 -1
- package/md_cg/mcp_server.py +362 -43
- package/md_cg/md_whitebox.py +53 -1
- package/md_cg/mdcg.py +1003 -27
- package/md_cg/mdcos.py +558 -36
- package/md_cg/metacognition.py +37 -2
- 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/__main__.py +3 -0
- package/md_cg/mreview/bundle.py +8 -0
- package/md_cg/mreview/candidates.py +9 -0
- package/md_cg/mreview/govern.py +21 -1
- package/md_cg/mreview/locate.py +34 -0
- package/md_cg/mreview/pipeline.py +29 -1
- package/md_cg/mreview/ruleset.py +16 -1
- package/md_cg/nodefile.py +233 -3
- package/md_cg/pooling.py +23 -1
- package/md_cg/postings.py +298 -0
- package/md_cg/predict.py +89 -9
- package/md_cg/progressive.py +3 -0
- package/md_cg/protect.py +14 -1
- 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 +47 -2
- 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 +63 -2
- package/md_cg/security.py +26 -1
- package/md_cg/self_state.py +64 -1
- package/md_cg/selfreport.py +151 -0
- package/md_cg/semantic/canonical.py +5 -0
- package/md_cg/semantic/en_normalizer.py +364 -355
- 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 +56 -1
- package/md_cg/tasks.py +26 -2
- 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_ccgc.py +10 -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_d_meta.py +412 -0
- package/md_cg/test_datapath_root.py +188 -0
- package/md_cg/test_gain_gate.py +47 -1
- package/md_cg/test_hot_cold.py +187 -0
- package/md_cg/test_hyperedge.py +245 -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 +108 -18
- package/md_cg/test_mr_m3.py +8 -1
- package/md_cg/test_p26_refindex.py +49 -20
- package/md_cg/test_p27_docindex.py +236 -2
- package/md_cg/test_p2_mcp.py +1 -1
- package/md_cg/test_p31_insight.py +24 -0
- 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 +8 -4
- 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 +59 -2
- package/md_cg/test_role_views.py +354 -0
- package/md_cg/test_subproc_encoding.py +188 -0
- package/md_cg/test_trust.py +361 -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 +5 -1
- package/md_cg/theory.py +16 -1
- package/md_cg/tokens.py +40 -8
- package/md_cg/tool_face.py +13 -2
- package/md_cg/trust.py +943 -0
- package/md_cg/twophase.py +12 -1
- package/md_cg/units.py +132 -10
- package/md_cg/vision_evidence.py +24 -1
- package/md_cg/weights.py +24 -1
- package/md_cg/whitebox.py +32 -1
- package/md_cg/whitebox_kb/data/verify_cache.json +21210 -365
- package/md_cg/whitebox_kb/data/verify_savings.jsonl +5078 -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 +18 -4
- package/md_cg/writepipe.py +178 -7
- 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 +17 -21
- package/src/index.ts +33 -6
- package/src/lib/datapath.ts +211 -13
- package/src/lib/mdcg_client.ts +64 -25
- package/src/lib/mutual.ts +411 -411
- package/src/lib/token_store.ts +4 -5
- package/zcode/AGENTS.md +196 -195
- package/zcode/README.md +4 -0
- package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db-shm +0 -0
- package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db-wal +0 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""md_cg · 热路径缓存(L0 节点缓存 + query 结果缓存)
|
|
3
|
+
|
|
4
|
+
【为什么】检索链路每次都跑四路 RRF 融合(约 50ms),但同一 query
|
|
5
|
+
短时间内反复调用是常态(agent 对话轮次间、batch 调用等)。热缓存
|
|
6
|
+
让重复 query 毫秒级返回,同时 L0 高频节点(session=main)的
|
|
7
|
+
frontmatter 读取也从磁盘降为内存命中。
|
|
8
|
+
|
|
9
|
+
【设计】
|
|
10
|
+
- 节点缓存:LRU dict,key=node_id → value=(frontmatter, body_excerpt)
|
|
11
|
+
- query 缓存:LRU dict,key=(query, k, layer, session, branch, validity, view,
|
|
12
|
+
extra) → value=(results, meta)
|
|
13
|
+
**键必须覆盖全部影响结果的参数**(红线,v14 缺陷 C 的教训):validity /
|
|
14
|
+
view 之外,`include_work` / `roles` / `paths` / `path_weights` /
|
|
15
|
+
`recall_only` / `fusion` / `judge` / `judge_ranking` / `goal_text` /
|
|
16
|
+
`context` / `early_stop_threshold` 同样改变候选资格与排序,经 `extra`
|
|
17
|
+
整体入键(`_KEYED_EXTRA` 为清单真源)。
|
|
18
|
+
不入键的后果不是「慢」而是**静默错答**:默认查询会返回工作角色节点
|
|
19
|
+
(资格泄漏)、`paths` 口径互相顶替等。
|
|
20
|
+
- 无法稳定规范化的参数(自定义可调用 `query_expand`,见 `_BYPASS_EXTRA`):
|
|
21
|
+
**非默认值即绕行缓存**(读+写双侧),fail-closed——宁可不用缓存,不可串味。
|
|
22
|
+
- 失效策略:写入时 invalidate 受影响节点 + query 全清(保守策略,
|
|
23
|
+
因为 RRF 融合后一个节点的变动可能影响全局排序)
|
|
24
|
+
- TTL:query 缓存 300s(5 分钟),节点缓存无 TTL(写入即失效)
|
|
25
|
+
|
|
26
|
+
【边界】
|
|
27
|
+
- 进程内缓存,不跨进程(每个 cg 实例独立)
|
|
28
|
+
- 不改 RRF 核心算法(bench6 600/600 rank 逐位一致是硬约束)
|
|
29
|
+
- 缓存命中时 meta 带 "cached": True 标记
|
|
30
|
+
|
|
31
|
+
零第三方依赖(D-005)。
|
|
32
|
+
"""
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import time
|
|
36
|
+
from collections import OrderedDict
|
|
37
|
+
|
|
38
|
+
#: 节点缓存上限
|
|
39
|
+
MAX_NODES = 256
|
|
40
|
+
#: query 缓存上限
|
|
41
|
+
MAX_QUERIES = 64
|
|
42
|
+
#: query 缓存 TTL(秒)
|
|
43
|
+
QUERY_TTL = 300.0
|
|
44
|
+
|
|
45
|
+
#: 进键的「口径参数」清单(**单一真源**):调用方(mdcos.search_rrf)按此
|
|
46
|
+
#: 构造 extra。新增影响结果的检索参数必须同时登记到本清单——漏登即跨口径
|
|
47
|
+
#: 串味(v14 缺陷 C:include_work/roles 致资格泄漏、paths 互相顶替)。
|
|
48
|
+
_KEYED_EXTRA = ("include_work", "roles", "paths", "path_weights", "recall_only",
|
|
49
|
+
"fusion", "judge", "judge_ranking", "goal_text", "context",
|
|
50
|
+
"early_stop_threshold")
|
|
51
|
+
#: 不可稳定规范化的参数(自定义可调用):非默认即**绕行缓存**(fail-closed)。
|
|
52
|
+
_BYPASS_EXTRA = ("query_expand",)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class HotCache:
|
|
56
|
+
"""进程内热路径缓存。
|
|
57
|
+
|
|
58
|
+
使用 OrderedDict 实现 LRU 语义:
|
|
59
|
+
- get 命中时 move_to_end(最近使用放尾)
|
|
60
|
+
- put 满时 popitem(last=False)(淘汰头=最久未用)
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, *, max_nodes=MAX_NODES, max_queries=MAX_QUERIES,
|
|
64
|
+
query_ttl=QUERY_TTL):
|
|
65
|
+
self._nodes: OrderedDict = OrderedDict()
|
|
66
|
+
self._queries: OrderedDict = OrderedDict()
|
|
67
|
+
self._max_nodes = max_nodes
|
|
68
|
+
self._max_queries = max_queries
|
|
69
|
+
self._query_ttl = query_ttl
|
|
70
|
+
# 统计
|
|
71
|
+
self._stats = {"node_hits": 0, "node_misses": 0,
|
|
72
|
+
"query_hits": 0, "query_misses": 0,
|
|
73
|
+
"invalidations": 0}
|
|
74
|
+
|
|
75
|
+
# ---------- 节点缓存 ----------
|
|
76
|
+
|
|
77
|
+
def get_node(self, node_id: str):
|
|
78
|
+
"""返回 (frontmatter, body_excerpt) 或 None(未缓存)。"""
|
|
79
|
+
entry = self._nodes.get(node_id)
|
|
80
|
+
if entry is None:
|
|
81
|
+
self._stats["node_misses"] += 1
|
|
82
|
+
return None
|
|
83
|
+
self._stats["node_hits"] += 1
|
|
84
|
+
self._nodes.move_to_end(node_id)
|
|
85
|
+
return entry
|
|
86
|
+
|
|
87
|
+
def put_node(self, node_id: str, frontmatter: dict, body_excerpt: str = ""):
|
|
88
|
+
"""写入/更新节点缓存。"""
|
|
89
|
+
self._nodes[node_id] = (dict(frontmatter), body_excerpt)
|
|
90
|
+
self._nodes.move_to_end(node_id)
|
|
91
|
+
while len(self._nodes) > self._max_nodes:
|
|
92
|
+
self._nodes.popitem(last=False)
|
|
93
|
+
|
|
94
|
+
def invalidate_node(self, node_id: str):
|
|
95
|
+
"""失效单个节点。"""
|
|
96
|
+
self._nodes.pop(node_id, None)
|
|
97
|
+
|
|
98
|
+
# ---------- query 缓存 ----------
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def _canon(v):
|
|
102
|
+
"""参数值 → 可哈希、稳定、跨调用一致的键片段(递归规范化容器)。"""
|
|
103
|
+
if v is None or isinstance(v, (bool, int, float, str, bytes)):
|
|
104
|
+
return v
|
|
105
|
+
if isinstance(v, (list, tuple)):
|
|
106
|
+
return tuple(HotCache._canon(x) for x in v)
|
|
107
|
+
if isinstance(v, (set, frozenset)):
|
|
108
|
+
return tuple(sorted((HotCache._canon(x) for x in v), key=repr))
|
|
109
|
+
if isinstance(v, dict):
|
|
110
|
+
return tuple(sorted((str(k), HotCache._canon(x))
|
|
111
|
+
for k, x in v.items()))
|
|
112
|
+
# 其余(含自定义对象/可调用):repr 含内存地址 → 每次不同 →
|
|
113
|
+
# 缓存永不命中(等价绕行),是**安全侧**失败(不串味)。
|
|
114
|
+
return ("repr", repr(v))
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
def _query_key(query, k, layer, session, branch, validity=None,
|
|
118
|
+
view=None, extra=None):
|
|
119
|
+
# 键必须覆盖全部影响结果的参数(正确性缺陷,非优化项):
|
|
120
|
+
# validity(不过滤/排已过期)、view(角色视图)、extra(口径参数,
|
|
121
|
+
# 见 _KEYED_EXTRA)任一不入键都会串结果。
|
|
122
|
+
return (query, k, layer or None, session or None, branch or None,
|
|
123
|
+
bool(validity) or None, view or None,
|
|
124
|
+
HotCache._canon(extra) if extra else None)
|
|
125
|
+
|
|
126
|
+
def get_query(self, query, k=20, layer=None, session=None, branch=None,
|
|
127
|
+
validity=None, view=None, extra=None):
|
|
128
|
+
"""返回 (results, meta) 或 None(未缓存/过期)。"""
|
|
129
|
+
key = self._query_key(query, k, layer, session, branch, validity,
|
|
130
|
+
view, extra)
|
|
131
|
+
entry = self._queries.get(key)
|
|
132
|
+
if entry is None:
|
|
133
|
+
self._stats["query_misses"] += 1
|
|
134
|
+
return None
|
|
135
|
+
ts, results, meta = entry
|
|
136
|
+
if time.time() - ts > self._query_ttl:
|
|
137
|
+
self._queries.pop(key, None)
|
|
138
|
+
self._stats["query_misses"] += 1
|
|
139
|
+
return None
|
|
140
|
+
self._stats["query_hits"] += 1
|
|
141
|
+
self._queries.move_to_end(key)
|
|
142
|
+
return results, dict(meta)
|
|
143
|
+
|
|
144
|
+
def put_query(self, query, results, meta, k=20, layer=None,
|
|
145
|
+
session=None, branch=None, validity=None, view=None,
|
|
146
|
+
extra=None):
|
|
147
|
+
"""写入/更新 query 缓存。"""
|
|
148
|
+
key = self._query_key(query, k, layer, session, branch, validity,
|
|
149
|
+
view, extra)
|
|
150
|
+
self._queries[key] = (time.time(), list(results), dict(meta))
|
|
151
|
+
self._queries.move_to_end(key)
|
|
152
|
+
while len(self._queries) > self._max_queries:
|
|
153
|
+
self._queries.popitem(last=False)
|
|
154
|
+
|
|
155
|
+
def invalidate_all_queries(self):
|
|
156
|
+
"""清空全部 query 缓存(写入时保守全清)。"""
|
|
157
|
+
n = len(self._queries)
|
|
158
|
+
self._queries.clear()
|
|
159
|
+
if n:
|
|
160
|
+
self._stats["invalidations"] += 1
|
|
161
|
+
|
|
162
|
+
def invalidate_node_and_queries(self, node_id: str):
|
|
163
|
+
"""失效节点 + 全清 query(节点变动可能影响全局排序)。"""
|
|
164
|
+
self.invalidate_node(node_id)
|
|
165
|
+
self.invalidate_all_queries()
|
|
166
|
+
|
|
167
|
+
# ---------- 全局 ----------
|
|
168
|
+
|
|
169
|
+
def clear(self):
|
|
170
|
+
"""清空全部缓存。"""
|
|
171
|
+
self._nodes.clear()
|
|
172
|
+
self._queries.clear()
|
|
173
|
+
|
|
174
|
+
def stats(self):
|
|
175
|
+
"""返回统计信息。"""
|
|
176
|
+
s = dict(self._stats)
|
|
177
|
+
s["node_cache_size"] = len(self._nodes)
|
|
178
|
+
s["query_cache_size"] = len(self._queries)
|
|
179
|
+
return s
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
#: 进程级默认实例(每个 cg 实例可独立持有,也可共享此单例)
|
|
183
|
+
_DEFAULT = None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def default_cache():
|
|
187
|
+
"""进程级默认热缓存单例。"""
|
|
188
|
+
global _DEFAULT
|
|
189
|
+
if _DEFAULT is None:
|
|
190
|
+
_DEFAULT = HotCache()
|
|
191
|
+
return _DEFAULT
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def attach(cg):
|
|
195
|
+
"""给 cg 实例挂热缓存(幂等:已挂则不重复)。"""
|
|
196
|
+
if not hasattr(cg, "_hotcache") or cg._hotcache is None:
|
|
197
|
+
cg._hotcache = HotCache()
|
|
198
|
+
return cg._hotcache
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def get(cg):
|
|
202
|
+
"""取 cg 实例的热缓存(未挂返回 None)。"""
|
|
203
|
+
return getattr(cg, "_hotcache", None)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def invalidate(cg, node_id: str = None):
|
|
207
|
+
"""写入后失效(节点 + query 全清)。node_id=None 时全清。"""
|
|
208
|
+
hc = get(cg)
|
|
209
|
+
if hc is None:
|
|
210
|
+
return
|
|
211
|
+
if node_id:
|
|
212
|
+
hc.invalidate_node_and_queries(node_id)
|
|
213
|
+
else:
|
|
214
|
+
hc.clear()
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""超边(hyperedge):跨端验证回执的认知图形式化投影。
|
|
3
|
+
|
|
4
|
+
上游真源 = 互验协议运行时文件(hive/interop/_receipts_ledger.tsv,六列);
|
|
5
|
+
超边节点是历史回执在认知图中的**形式化镜像**(一行 = 一条超边,回执粒度),
|
|
6
|
+
单向投影:超边不反向覆写台账,协议运行时逻辑零改动。
|
|
7
|
+
|
|
8
|
+
六列 → fm + 正文完整自包含回执卡,机械映射零编造:
|
|
9
|
+
received_at / verify_id / commit / verdict / receipt_job / iter_id
|
|
10
|
+
- participants 定长二元 [verify_id, receipt_job](六列机械可得的全部参与者
|
|
11
|
+
信息;role/harness/unit/session 归因字段台账无载,不编造)
|
|
12
|
+
- evidence [{verify_id, commit, verdict, received_at}]
|
|
13
|
+
- corrections [](迁移期恒空;后续跨端修正经修正面回填)
|
|
14
|
+
- ledger_row 六列整行锚(与结构键互为回放校验)
|
|
15
|
+
|
|
16
|
+
幂等键:he_<sha1(六列值 join)[:10]>——同一台账行重复迁移得到同一节点 id。
|
|
17
|
+
|
|
18
|
+
验证器 verify_payload 做「结构键反解 ↔ ledger_row 锚」逐字段回放比对 + 正文
|
|
19
|
+
四段标记检查,经 register_verifier("hyperedge", …) 注入(或 MDCG_VERIFIER_MODULES
|
|
20
|
+
外部注入;未注入时 audit DEFER——诚实不假装通过)。
|
|
21
|
+
|
|
22
|
+
本模块不含写库逻辑;写入由调用方走 cg(op=write, content_kind="hyperedge", …)
|
|
23
|
+
经 writepipe 既有审核链(五动词纪律:不重复造写入通道)。
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import hashlib
|
|
28
|
+
|
|
29
|
+
CONTENT_KIND = "hyperedge"
|
|
30
|
+
EDGE_TYPE = "cross_verify" # 唯一类型,留扩展位
|
|
31
|
+
SOURCE_NAME = "receipts_ledger" # 溯源锚(协议真源 TSV)
|
|
32
|
+
LEDGER_COLUMNS = ("received_at", "verify_id", "commit", "verdict",
|
|
33
|
+
"receipt_job", "iter_id")
|
|
34
|
+
|
|
35
|
+
# 写入面透传给 cg.add(**extra) 的 fm 键集合(writepipe._hyperedge_extra 按此
|
|
36
|
+
# 收集非 None 键)。落盘面丢字段 = 上游声明静默失效(比报错更难发现)。
|
|
37
|
+
EXTRA_FM_KEYS = ("content_kind", "edge_type", "participants", "evidence",
|
|
38
|
+
"corrections", "iter_id", "source", "received_at",
|
|
39
|
+
"verdict", "ledger_row")
|
|
40
|
+
|
|
41
|
+
# 正文四段标记(build_content 段落标题词;验证器检查齐备——无 fm 解析能力
|
|
42
|
+
# 的外部工具直读正文即可取数,这是「可被外部理解的正式结构」的最低保障)。
|
|
43
|
+
CONTENT_MARKERS = ("参与者", "证据", "修正", "时间")
|
|
44
|
+
|
|
45
|
+
_EMPTY_NOTE = "(台账原始行无此字段)" # 空字段的事实陈述(非编造占位)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# 生效条件:v 传入即归一为去空格字符串(None 得空串),无条件分支;
|
|
49
|
+
def _clean(v):
|
|
50
|
+
return "" if v is None else str(v).strip()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# 生效条件:row 为含六列键的 dict 时返回六列清洗副本;缺列或非 dict 即抛
|
|
54
|
+
# ValueError(fail-closed,不猜测补值);
|
|
55
|
+
def _norm_row(row):
|
|
56
|
+
if not isinstance(row, dict):
|
|
57
|
+
raise ValueError("台账行须为 dict(六列键),得到 %s" % type(row).__name__)
|
|
58
|
+
missing = [c for c in LEDGER_COLUMNS if c not in row]
|
|
59
|
+
if missing:
|
|
60
|
+
raise ValueError("台账行缺列:%s(fail-closed,不猜测补值)" % missing)
|
|
61
|
+
return {c: _clean(row.get(c)) for c in LEDGER_COLUMNS}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# 生效条件:row 经 _norm_row 校验即返回固定键集 fm(content_kind/edge_type/
|
|
65
|
+
# participants/evidence/corrections/iter_id/source/received_at/verdict/ledger_row);
|
|
66
|
+
def fm_from_row(row):
|
|
67
|
+
"""台账行 → 超边 fm(schema v0.1,协议冻结前允许破坏性调整)。"""
|
|
68
|
+
r = _norm_row(row)
|
|
69
|
+
return {
|
|
70
|
+
"content_kind": CONTENT_KIND,
|
|
71
|
+
"edge_type": EDGE_TYPE,
|
|
72
|
+
# 定长二元:空串占位保证反解对称([0]→verify_id、[1]→receipt_job)。
|
|
73
|
+
"participants": [r["verify_id"], r["receipt_job"]],
|
|
74
|
+
"evidence": [{"verify_id": r["verify_id"], "commit": r["commit"],
|
|
75
|
+
"verdict": r["verdict"], "received_at": r["received_at"]}],
|
|
76
|
+
"corrections": [],
|
|
77
|
+
"iter_id": r["iter_id"],
|
|
78
|
+
"source": SOURCE_NAME,
|
|
79
|
+
"received_at": r["received_at"],
|
|
80
|
+
"verdict": r["verdict"],
|
|
81
|
+
"ledger_row": dict(r),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# 生效条件:fm 的 participants 为定长二元、evidence 为单项列表时返回六列重建
|
|
86
|
+
# 行(回放方向 H1:反解六列);形态不符即抛 ValueError;
|
|
87
|
+
def rows_from_fm(fm):
|
|
88
|
+
"""fm 结构键 → 台账行(反解方向,不依赖 ledger_row 锚)。"""
|
|
89
|
+
parts = fm.get("participants")
|
|
90
|
+
if not isinstance(parts, list) or len(parts) != 2:
|
|
91
|
+
raise ValueError("participants 须为定长二元 [verify_id, receipt_job]")
|
|
92
|
+
ev = fm.get("evidence")
|
|
93
|
+
if not isinstance(ev, list) or len(ev) != 1 or not isinstance(ev[0], dict):
|
|
94
|
+
raise ValueError("evidence 须为单项列表"
|
|
95
|
+
"({verify_id, commit, verdict, received_at})")
|
|
96
|
+
e = ev[0]
|
|
97
|
+
return {
|
|
98
|
+
"received_at": _clean(e.get("received_at")),
|
|
99
|
+
"verify_id": _clean(parts[0]),
|
|
100
|
+
"commit": _clean(e.get("commit")),
|
|
101
|
+
"verdict": _clean(e.get("verdict")),
|
|
102
|
+
"receipt_job": _clean(parts[1]),
|
|
103
|
+
"iter_id": _clean(fm.get("iter_id")),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# 生效条件:fm 含六列信息(ledger_row 锚优先,缺锚走结构键反解)时返回六列行;
|
|
108
|
+
# 两者皆不可解析即抛 ValueError;
|
|
109
|
+
def _row_of_fm(fm):
|
|
110
|
+
anchored = fm.get("ledger_row")
|
|
111
|
+
if isinstance(anchored, dict):
|
|
112
|
+
return {c: _clean(anchored.get(c)) for c in LEDGER_COLUMNS}
|
|
113
|
+
return rows_from_fm(fm)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# 生效条件:fm 经 _row_of_fm 可解析即返回含四段标记(参与者/证据/修正/时间)
|
|
117
|
+
# 的正文,空字段以「台账原始行无此字段」事实陈述呈现;
|
|
118
|
+
def build_content(fm):
|
|
119
|
+
"""超边 fm → 四段正文(参与者/证据/修正/时间)。"""
|
|
120
|
+
r = _row_of_fm(fm)
|
|
121
|
+
|
|
122
|
+
def v(col):
|
|
123
|
+
return r[col] if r[col] else _EMPTY_NOTE
|
|
124
|
+
|
|
125
|
+
corr = fm.get("corrections") or []
|
|
126
|
+
lines = [
|
|
127
|
+
"# 跨端验证超边(%s / %s)" % (CONTENT_KIND, EDGE_TYPE),
|
|
128
|
+
"",
|
|
129
|
+
"## 参与者",
|
|
130
|
+
"- verify_id: %s" % v("verify_id"),
|
|
131
|
+
"- receipt_job: %s" % v("receipt_job"),
|
|
132
|
+
"",
|
|
133
|
+
"## 证据",
|
|
134
|
+
"- commit: %s" % v("commit"),
|
|
135
|
+
"- verdict: %s" % v("verdict"),
|
|
136
|
+
"- received_at: %s" % v("received_at"),
|
|
137
|
+
"",
|
|
138
|
+
"## 修正",
|
|
139
|
+
]
|
|
140
|
+
lines.extend(("- %s" % _clean(c) for c in corr if _clean(c)) if corr
|
|
141
|
+
else ["无(迁移期恒空;后续跨端修正经修正面回填)"])
|
|
142
|
+
lines.extend([
|
|
143
|
+
"",
|
|
144
|
+
"## 时间",
|
|
145
|
+
"- received_at: %s" % v("received_at"),
|
|
146
|
+
"- iter_id: %s" % v("iter_id"),
|
|
147
|
+
"",
|
|
148
|
+
"溯源:%s(TSV 真源单向投影,超边不反向覆写台账)" % SOURCE_NAME,
|
|
149
|
+
])
|
|
150
|
+
return "\n".join(lines)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# 生效条件:row 经 _norm_row 校验即返回载荷 {ledger_row, frontmatter, content}
|
|
154
|
+
# (写入面读向齐备,幂等 id 另经 node_id_for 取);
|
|
155
|
+
def row_from_ledger(row):
|
|
156
|
+
"""台账行 → 超边载荷({ledger_row, frontmatter, content})。"""
|
|
157
|
+
r = _norm_row(row)
|
|
158
|
+
fm = fm_from_row(r)
|
|
159
|
+
return {"ledger_row": dict(r), "frontmatter": fm,
|
|
160
|
+
"content": build_content(fm)}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# 生效条件:node 为 dict(frontmatter 嵌套或 fm 键平铺两形态)且结构键可反解
|
|
164
|
+
# 时返回载荷(ledger_row 优先取节点锚,缺锚用结构重建行);结构不可反解即抛
|
|
165
|
+
# ValueError;
|
|
166
|
+
def payload_from_node(node):
|
|
167
|
+
"""节点 → 载荷(回放重建读向 H1)。"""
|
|
168
|
+
if not isinstance(node, dict):
|
|
169
|
+
raise ValueError("节点须为 dict")
|
|
170
|
+
fm = node.get("frontmatter") if isinstance(node.get("frontmatter"), dict) \
|
|
171
|
+
else node
|
|
172
|
+
anchored = fm.get("ledger_row")
|
|
173
|
+
rebuilt = rows_from_fm(fm)
|
|
174
|
+
row = ({c: _clean(anchored.get(c)) for c in LEDGER_COLUMNS}
|
|
175
|
+
if isinstance(anchored, dict) else rebuilt)
|
|
176
|
+
return {"ledger_row": row, "frontmatter": fm,
|
|
177
|
+
"content": node.get("content") or ""}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# 生效条件:a(writepipe 写入请求,fm 键平铺顶层)传入即返回验证器三键载荷
|
|
181
|
+
# {ledger_row, frontmatter, content}——frontmatter 收集 a 中非 None 的
|
|
182
|
+
# EXTRA_FM_KEYS 键;ledger_row 非 dict 时为 None(由验证器 fail-closed 裁决);
|
|
183
|
+
def audit_payload(a):
|
|
184
|
+
"""writepipe audit 闸的超边载荷装配(fm 键平铺 → 三键载荷)。"""
|
|
185
|
+
row = a.get("ledger_row")
|
|
186
|
+
return {"ledger_row": row if isinstance(row, dict) else None,
|
|
187
|
+
"frontmatter": {k: a.get(k) for k in EXTRA_FM_KEYS
|
|
188
|
+
if a.get(k) is not None},
|
|
189
|
+
"content": a.get("content") or ""}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# 生效条件:row 经 _norm_row 校验即返回 he_<sha1(逐列「len:val」拼接)[:10]>
|
|
193
|
+
# (同一台账行恒同 id,重复迁移幂等;任意列内容不可伪造边界);
|
|
194
|
+
def node_id_for(row):
|
|
195
|
+
"""幂等键:同一台账行重复迁移得到同一节点 id。
|
|
196
|
+
|
|
197
|
+
**无歧义规范化**(2026-09-20 v14 缺陷 G 修复):逐列 `<len>:<val>` 前缀
|
|
198
|
+
拼接——长度前缀由本函数计算、不可被列内容伪造,故任意两列之间搬移含任意
|
|
199
|
+
字符的文本都得不同 canon。旧实现直接 `"\\x1f".join(...)`:`verify_id="a"`
|
|
200
|
+
+ `commit="b\\x1fc"` 与 `verify_id="a\\x1fb"` + `commit="c"` 撞同一 id
|
|
201
|
+
(不是 SHA-1 碰撞,是规范化没做转义/长度前缀)。
|
|
202
|
+
|
|
203
|
+
id 口径变更说明:canon 输入形态改变 → id 取值随之改变。属 schema v0.1
|
|
204
|
+
内的破坏性调整(该 schema 明示允许);实测库内 `he_` 节点为 0,无历史
|
|
205
|
+
迁移数据需兼容。
|
|
206
|
+
"""
|
|
207
|
+
r = _norm_row(row)
|
|
208
|
+
canon = "".join("%d:%s" % (len(r[c]), r[c]) for c in LEDGER_COLUMNS)
|
|
209
|
+
return "he_" + hashlib.sha1(canon.encode("utf-8")).hexdigest()[:10]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# 生效条件:payload 缺 ledger_row/frontmatter、结构键不可反解、反解与锚不一致
|
|
213
|
+
# 或正文缺四段标记即 REJECT;逐字段一致且标记齐备即 ACCEPT(detail 带幂等 id);
|
|
214
|
+
# 其它异常照抛(由 audit 包为 DEFER);
|
|
215
|
+
def verify_payload(payload, ctx=None):
|
|
216
|
+
"""audit 验证器签名:回放比对(fm/content 反解 ↔ ledger_row 锚)。"""
|
|
217
|
+
if not isinstance(payload, dict):
|
|
218
|
+
return {"state": "REJECT", "evidence": "payload 须为 dict,得到 %s"
|
|
219
|
+
% type(payload).__name__}
|
|
220
|
+
row = payload.get("ledger_row")
|
|
221
|
+
fm = payload.get("frontmatter")
|
|
222
|
+
if not isinstance(fm, dict):
|
|
223
|
+
fm = payload # 平铺形态:载荷自身承载 fm 键(writepipe 闸外直调)
|
|
224
|
+
if not isinstance(row, dict):
|
|
225
|
+
return {"state": "REJECT",
|
|
226
|
+
"evidence": "payload 缺 ledger_row 锚(回执卡不自包含)"}
|
|
227
|
+
try:
|
|
228
|
+
anchored = {c: _clean(row.get(c)) for c in LEDGER_COLUMNS}
|
|
229
|
+
rebuilt = rows_from_fm(fm) # 形态坏 → ValueError → REJECT
|
|
230
|
+
except ValueError as exc:
|
|
231
|
+
return {"state": "REJECT", "evidence": str(exc)}
|
|
232
|
+
diffs = ["%s: %r != %r" % (c, anchored[c], rebuilt[c])
|
|
233
|
+
for c in LEDGER_COLUMNS if anchored[c] != rebuilt[c]]
|
|
234
|
+
if diffs:
|
|
235
|
+
return {"state": "REJECT", "evidence": "回放比对不一致:%s" % "; ".join(diffs),
|
|
236
|
+
"detail": {"field_diffs": diffs}}
|
|
237
|
+
ctext = payload.get("content") if isinstance(payload.get("content"), str) else ""
|
|
238
|
+
miss = [m for m in CONTENT_MARKERS if m not in ctext]
|
|
239
|
+
if miss:
|
|
240
|
+
return {"state": "REJECT", "evidence": "正文缺四段标记:%s(不自包含)" % miss}
|
|
241
|
+
return {"state": "ACCEPT",
|
|
242
|
+
"evidence": "回放重建六列逐字段一致(verify_id=%r)"
|
|
243
|
+
% anchored["verify_id"],
|
|
244
|
+
"detail": {"node_id": node_id_for(anchored)}}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# 生效条件:audit_mod 提供 register_verifier 即把 verify_payload 以 override=True
|
|
248
|
+
# 注入(重复加载幂等);
|
|
249
|
+
def register(audit_mod):
|
|
250
|
+
"""MDCG_VERIFIER_MODULES 入口:把回放比对验证器注入 audit。"""
|
|
251
|
+
audit_mod.register_verifier(CONTENT_KIND, verify_payload, override=True)
|
package/md_cg/identity.py
CHANGED
|
@@ -92,6 +92,7 @@ _POSITION_TAGS = {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
|
|
95
|
+
# 生效条件:subject_id 为假值(None/空串)时 s 为空串、无 ":" 可切,返回 ("user","");否则 strip 后含 ":" 则按首个冒号切成 kind/name,kind 经 strip().lower() 后不在 SUBJECT_KIND 即改记 "user",不含 ":" 时整串作为 name 归 "user"。
|
|
95
96
|
def split_subject(subject_id):
|
|
96
97
|
"""`"self:lingshu"` → `("self", "lingshu")`;无前缀按 `user` 处理。"""
|
|
97
98
|
s = (subject_id or "").strip()
|
|
@@ -104,21 +105,25 @@ def split_subject(subject_id):
|
|
|
104
105
|
return "user", s
|
|
105
106
|
|
|
106
107
|
|
|
108
|
+
# 生效条件:对 str(s) 的每个字符,isalnum() 为真或字符属于 "_-" 时原样保留,其余一律替换为 "_"(s 为 None/非字符串时按 str(s) 结果逐字符处理)。
|
|
107
109
|
def _slug(s):
|
|
108
110
|
return "".join(ch if (ch.isalnum() or ch in "_-") else "_" for ch in str(s))
|
|
109
111
|
|
|
110
112
|
|
|
113
|
+
# 生效条件:subject_id 经 split_subject 与 _slug 处理后,按 PROFILE_PREFIX + kind + "_" + name 拼出档案节点 id。
|
|
111
114
|
def subject_node_id(subject_id):
|
|
112
115
|
"""主体的档案节点 id(锚点载体):`identity_<kind>_<name>`。"""
|
|
113
116
|
kind, name = split_subject(subject_id)
|
|
114
117
|
return f"{PROFILE_PREFIX}{_slug(kind)}_{_slug(name)}"
|
|
115
118
|
|
|
116
119
|
|
|
120
|
+
# 生效条件:给定 prefix 与 subject_id(seed 缺省为空串且原样参与哈希,不做回落)即返回 f"{PROFILE_PREFIX}{prefix}_{_slug(subject_id)}_{sha256(f'{subject_id}|{seed}|{time.time()}')前10位}"——prefix 只参与结果拼接、不参与哈希,因哈希含 time.time() 故每次调用返回的后缀都不同。
|
|
117
121
|
def _nid(prefix, subject_id, seed=""):
|
|
118
122
|
h = hashlib.sha256(f"{subject_id}|{seed}|{time.time()}".encode("utf-8"))
|
|
119
123
|
return f"{PROFILE_PREFIX}{prefix}_{_slug(subject_id)}_{h.hexdigest()[:10]}"
|
|
120
124
|
|
|
121
125
|
|
|
126
|
+
# 生效条件:rec 先被 dict(rec) 拷贝,仅当拷贝中缺 "ts" 键时补 time.time(),随后写入 cg.root 下 AUDIT_FILE 指向的 jsonl;写盘抛 OSError 时静默跳过并返回 None。
|
|
122
127
|
def log(cg, rec):
|
|
123
128
|
rec = dict(rec)
|
|
124
129
|
rec.setdefault("ts", time.time())
|
|
@@ -128,6 +133,7 @@ def log(cg, rec):
|
|
|
128
133
|
pass
|
|
129
134
|
|
|
130
135
|
|
|
136
|
+
# 生效条件:cg.root 下 AUDIT_FILE 路径缺失或打开即抛 OSError 时返回空列表;否则逐行解析非空 JSON(json.loads 抛 ValueError 的行跳过),读取中途抛 OSError 时返回已解析的部分 out,正常返回 out[-int(limit or 100):],其中 limit 为 0/空串等假值时按 100 取值;
|
|
131
137
|
def history(cg, limit=100):
|
|
132
138
|
p = os.path.join(cg.root, AUDIT_FILE)
|
|
133
139
|
out = []
|
|
@@ -152,6 +158,7 @@ def history(cg, limit=100):
|
|
|
152
158
|
# 扮演论三接口:memory / anchor / values
|
|
153
159
|
# --------------------------------------------------------------------------
|
|
154
160
|
|
|
161
|
+
# 生效条件:subject_id 定 kind 默认值,kind 参数为真值时覆盖之;node_id 为假值时用 _nid("obs", subject_id, text[:32])(含 time.time())生成;标签为 list(tags or []) 追加 subject:<subject_id>、TAG_OBS、kind:<k>,evidence 为真值时再追加一条 evidence:<evidence>;condition_space 以 dict(condition_space or {}) 为底并 setdefault subject/kind;落层用 layer 真值否则 "knowledge",role 为真值时随 extra 传 role;返回 {"ok": True, "node_id", "subject_id", "kind"}。
|
|
155
162
|
def observe(cg, subject_id, text, *, kind=None, role=None, layer=None, tags=None,
|
|
156
163
|
condition_space=None, importance=0.5, verification_basis=None,
|
|
157
164
|
evidence=None, node_id=None, override=False):
|
|
@@ -178,6 +185,7 @@ def observe(cg, subject_id, text, *, kind=None, role=None, layer=None, tags=None
|
|
|
178
185
|
return {"ok": True, "node_id": nid, "subject_id": subject_id, "kind": k}
|
|
179
186
|
|
|
180
187
|
|
|
188
|
+
# 生效条件:kind 参数为真值时覆盖 split_subject(subject_id) 得到的类型,k 不在 SUBJECT_KIND 时抛 ValueError;k 为 "self" 落 "self" 层、其余落 "anchor" 层,但 requested_layer 等于 "self" 且 k 不为 "self" 时抛 ValueError(不写盘);节点 id 取自 subject_node_id(subject_id),返回含 layer 与 protected True。
|
|
181
189
|
def set_anchor(cg, subject_id, text, *, kind=None, condition_space=None,
|
|
182
190
|
importance=0.9, override=False, requested_layer=None):
|
|
183
191
|
"""anchor 接口:写入身份锚点(不可遗忘)。
|
|
@@ -209,6 +217,7 @@ def set_anchor(cg, subject_id, text, *, kind=None, condition_space=None,
|
|
|
209
217
|
"layer": layer, "protected": True}
|
|
210
218
|
|
|
211
219
|
|
|
220
|
+
# 生效条件:kind 参数为真值时覆盖 split_subject(subject_id) 得到的类型;node_id 为假值时用 _nid("trait", subject_id, trait[:24]) 生成;落层硬编码为 "structural";position 为真值时追加 position:<position> 标签;verification_basis 缺省为 "data",importance 缺省 0.6;返回 layer 为 "structural" 与所用 condition_space。
|
|
212
221
|
def add_trait(cg, subject_id, trait, *, condition_space=None, importance=0.6,
|
|
213
222
|
position=None, kind=None, verification_basis="data",
|
|
214
223
|
override=False, node_id=None):
|
|
@@ -238,6 +247,7 @@ def add_trait(cg, subject_id, trait, *, condition_space=None, importance=0.6,
|
|
|
238
247
|
# 位置效应推断(智能论 v3.4 §十三)
|
|
239
248
|
# --------------------------------------------------------------------------
|
|
240
249
|
|
|
250
|
+
# 生效条件:tags 先转为 set(tags or []) 后并列判定——role 属于 ("command","tool-output") 或 layer 等于 "contextual" 则加 "record";tags 与 _POSITION_TAGS 各项标记集有交集则加对应 pos;ev_pos 或 ev_neg 为真值则加 "verify";role 属于 ("user","assistant") 或 tags 中任一字符串以 "cap:" 开头则加 "output";layer 等于 "goals" 则加 "sustain";返回该并集。
|
|
241
251
|
def _votes(role, layer, tags, vb, ev_pos, ev_neg):
|
|
242
252
|
"""把一条行为证据映射到位置效应候选(可多面)。规则可审计、可扩展。"""
|
|
243
253
|
tags = set(tags or [])
|
|
@@ -257,6 +267,7 @@ def _votes(role, layer, tags, vb, ev_pos, ev_neg):
|
|
|
257
267
|
return v
|
|
258
268
|
|
|
259
269
|
|
|
270
|
+
# 生效条件:遍历 cg.index.get("nodes")(缺键或值为假时取空 dict,此时必然返回 position=unknown、confidence=0.0、votes={}),仅统计标签含 "subject:<subject_id>" 且不含 TAG_ANCHOR/TAG_TRAIT 的节点,票由 _votes 得出;无任何票时返回 unknown/0.0/{};有票时 winners 为 POSITION_ORDER 中票数等于最高票的位置,position 取 winners[0],confidence=round(top/total, 3),多赢家时以 tie 列出。
|
|
260
271
|
def infer_position(cg, subject_id):
|
|
261
272
|
"""按该主体的行为证据投票推断位置效应(带 confidence / votes,可审计)。
|
|
262
273
|
|
|
@@ -288,12 +299,14 @@ def infer_position(cg, subject_id):
|
|
|
288
299
|
"tie": winners if len(winners) > 1 else []}
|
|
289
300
|
|
|
290
301
|
|
|
302
|
+
# 生效条件:pos 取自 POSITIONS,缺键时以空 dict 兜底,故未知 pos 返回 {"position": pos, "unit": None, "effect": None, "duty": None}。
|
|
291
303
|
def _position_view(pos):
|
|
292
304
|
meta = POSITIONS.get(pos) or {}
|
|
293
305
|
return {"position": pos, "unit": meta.get("unit"),
|
|
294
306
|
"effect": meta.get("effect"), "duty": meta.get("duty")}
|
|
295
307
|
|
|
296
308
|
|
|
309
|
+
# 生效条件:仅遍历标签含 "subject:<subject_id>" 的节点,其中带 TAG_ANCHOR 者进 anchors,否则(elif)带 TAG_TRAIT 者进 traits——两标签同时存在时只进 anchors、不进 traits;anchor 取 anchors[0] 或 None;位置相关字段来自 infer_position(cg, subject_id)。
|
|
297
310
|
def profile(cg, subject_id):
|
|
298
311
|
"""主体画像 = 身份锚点 + 位置效应 + 条件特征(取代单一「用户画像」)。"""
|
|
299
312
|
k, name = split_subject(subject_id)
|
|
@@ -324,6 +337,7 @@ def profile(cg, subject_id):
|
|
|
324
337
|
return out
|
|
325
338
|
|
|
326
339
|
|
|
340
|
+
# 生效条件:counts 按每个以 "subject:" 开头的字符串标签出现次数累加(即标签次数,不是节点数),按 (-次数, sid) 排序后逐个 sid 调 infer_position;limit 为 0/None/空串等使 `limit and int(limit) > 0` 为假时返回全量 out,仅当 limit 为真值且 int(limit) > 0 时返回前 int(limit) 项。
|
|
327
341
|
def positions(cg, limit=0):
|
|
328
342
|
"""所有主体的位置效应分布(OS 视角:谁在记录/反思/验证/输出/维生)。"""
|
|
329
343
|
nodes = cg.index.get("nodes") or {}
|
|
@@ -342,6 +356,7 @@ def positions(cg, limit=0):
|
|
|
342
356
|
return out[:int(limit)] if limit and int(limit) > 0 else out
|
|
343
357
|
|
|
344
358
|
|
|
359
|
+
# 生效条件:cg.root/AUDIT_FILE 不存在时返回 0;否则逐行统计 strip() 后非空的行数,读取途中抛 OSError 时返回已累计的 n。
|
|
345
360
|
def _audit_count(cg):
|
|
346
361
|
p = os.path.join(cg.root, AUDIT_FILE)
|
|
347
362
|
if not os.path.exists(p):
|
|
@@ -357,6 +372,7 @@ def _audit_count(cg):
|
|
|
357
372
|
return n
|
|
358
373
|
|
|
359
374
|
|
|
375
|
+
# 生效条件:以 positions(cg)(limit 缺省 0,返回全量)得主体列表并按各自 position 聚合计数,与 _audit_count(cg) 一并返回 subjects/by_position/audit_records。
|
|
360
376
|
def summary(cg):
|
|
361
377
|
"""身份面汇总:主体数 + 位置分布(供 health / 运维审计,流式计数不载全量)。"""
|
|
362
378
|
ps = positions(cg)
|
|
@@ -367,8 +383,9 @@ def summary(cg):
|
|
|
367
383
|
"audit_records": _audit_count(cg)}
|
|
368
384
|
|
|
369
385
|
|
|
386
|
+
# 生效条件:无入参,恒返回 POSITION_ORDER 中每个 p 的 POSITIONS[p] 副本、INTERFACES 各值副本以及 list(SUBJECT_KIND)。
|
|
370
387
|
def catalog():
|
|
371
388
|
"""自描述:位置效应表 + 三接口(供 MCP / 文档对照协议验证)。"""
|
|
372
389
|
return {"positions": {p: dict(POSITIONS[p]) for p in POSITION_ORDER},
|
|
373
390
|
"interfaces": {k: dict(v) for k, v in INTERFACES.items()},
|
|
374
|
-
"kinds": list(SUBJECT_KIND)}
|
|
391
|
+
"kinds": list(SUBJECT_KIND)}
|