@furongjun1999/dsh-memory 0.4.8 → 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 +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 +6 -3
- package/lib/lib/mdcg_client.js +6 -5
- 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_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 +129 -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 +12 -8
- 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
|
@@ -26,6 +26,7 @@ for _p in (_ROOT, _HERE, os.path.join(_HERE, "whitebox_kb"),
|
|
|
26
26
|
from wisdom_book import (ConditionDex, DEFAULT_DB, DEFAULT_MD_ROOT, # noqa: E402
|
|
27
27
|
BASE_ENTRIES, BASE_RELATIONS)
|
|
28
28
|
from md_access import MdConn, read_conn # noqa: E402
|
|
29
|
+
from md_whitebox import corpus_ready # noqa: E402
|
|
29
30
|
from md_store import MdStore # noqa: E402
|
|
30
31
|
from aeis_core import ConditionSpace, MemoryLayer, STNode # noqa: E402
|
|
31
32
|
|
|
@@ -44,8 +45,11 @@ def ok(cond, label):
|
|
|
44
45
|
# ---------------------------------------------------------------------------
|
|
45
46
|
|
|
46
47
|
def group_a(tmp):
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
# 语料就绪判据 = 「目录存在**且含 .md**」(2026-09-20 v14 缺陷 F):空壳
|
|
49
|
+
# 目录(兄弟测试经 MdCGOS(root) 的 makedirs 留下)不算就绪,否则组A 会
|
|
50
|
+
# 以「md 语料行数 0 > 4000」的失败示人,而真实原因是真源根本不在位。
|
|
51
|
+
if not (os.path.exists(DEFAULT_DB) and corpus_ready(DEFAULT_MD_ROOT)):
|
|
52
|
+
print("组A SKIP:生产对拍数据不全(派生库缺失或语料根缺失/空壳)")
|
|
49
53
|
return
|
|
50
54
|
print("组A · 生产数据对拍(全量)")
|
|
51
55
|
# sqlite 侧用副本隔离:search_content 的 increment_access 会落库,
|
|
@@ -258,7 +262,7 @@ def group_d(tmp):
|
|
|
258
262
|
ok(not dex2._md_mode, "fresh=True → sqlite 形态(兼容 api.py 兜底)")
|
|
259
263
|
dex2.close()
|
|
260
264
|
|
|
261
|
-
if
|
|
265
|
+
if corpus_ready(DEFAULT_MD_ROOT):
|
|
262
266
|
dex3 = ConditionDex(md_root=DEFAULT_MD_ROOT)
|
|
263
267
|
try:
|
|
264
268
|
conn = read_conn(dex3) # duck:MdStore.conn → MdConn
|
package/md_cg/test_writepipe.py
CHANGED
|
@@ -129,8 +129,12 @@ def _run(tmp):
|
|
|
129
129
|
"content": "PASSED SKIPME",
|
|
130
130
|
"layer": "knowledge"})
|
|
131
131
|
_check("移除即恢复(核心文件零改动)", out.get("committed") is True, repr(out))
|
|
132
|
+
# 默认 before 链含 linkref→deps→audit→consistency→gated(2026-09-19 deps 闸门新增)
|
|
132
133
|
_check("单例默认链", singleton.names()["before"]
|
|
133
|
-
== ["audit", "consistency", "gated"],
|
|
134
|
+
== ["linkref", "deps", "audit", "consistency", "gated"],
|
|
135
|
+
repr(singleton.names()))
|
|
136
|
+
_check("单例默认after链", singleton.names()["after"]
|
|
137
|
+
== ["linkref", "trust"],
|
|
134
138
|
repr(singleton.names()))
|
|
135
139
|
|
|
136
140
|
# ---------- 3 链序 / 短路 / REWRITE ----------
|
package/md_cg/theory.py
CHANGED
|
@@ -52,24 +52,29 @@ class TheoryError(Exception):
|
|
|
52
52
|
# 存储
|
|
53
53
|
# --------------------------------------------------------------------------
|
|
54
54
|
|
|
55
|
+
# 生效条件:path 为真值时返回 path,否则取 os.environ.get(THEORY_FILE_ENV),该值为空串/未设置时回落模块常量 DEFAULT_THEORY_FILE。
|
|
55
56
|
def theory_file(path: str = None) -> str:
|
|
56
57
|
return path or os.environ.get(THEORY_FILE_ENV) or DEFAULT_THEORY_FILE
|
|
57
58
|
|
|
58
59
|
|
|
60
|
+
# 生效条件:传入可被 json 序列化的 payload(dict)时返回 json.dumps(payload, sort_keys=True, ensure_ascii=False, separators=(",", ":"))。
|
|
59
61
|
def _canon(payload: dict) -> str:
|
|
60
62
|
"""规范化 JSON:键排序 + 紧凑分隔符(哈希可复现的唯一前提)。"""
|
|
61
63
|
return json.dumps(payload, sort_keys=True, ensure_ascii=False,
|
|
62
64
|
separators=(",", ":"))
|
|
63
65
|
|
|
64
66
|
|
|
67
|
+
# 生效条件:payload 可被 _canon 序列化时返回 hashlib.sha256(_canon(payload).encode("utf-8")).hexdigest() 的十六进制摘要。
|
|
65
68
|
def declaration_hash(payload: dict) -> str:
|
|
66
69
|
return hashlib.sha256(_canon(payload).encode("utf-8")).hexdigest()
|
|
67
70
|
|
|
68
71
|
|
|
72
|
+
# 生效条件:对 SEALED_FIELDS 中每个 k 用 decl.get(k) 取值(缺键即 None),返回同集合长度的 dict。
|
|
69
73
|
def _payload(decl: dict) -> dict:
|
|
70
74
|
return {k: decl.get(k) for k in SEALED_FIELDS}
|
|
71
75
|
|
|
72
76
|
|
|
77
|
+
# 生效条件:以 dict(decl) 为副本 d,写入 d["declaration_hash"]=declaration_hash(_payload(d)),返回该副本(不改动入参 decl)。
|
|
73
78
|
def seal(decl: dict) -> dict:
|
|
74
79
|
"""封缄:补上 declaration_hash。"""
|
|
75
80
|
d = dict(decl)
|
|
@@ -77,6 +82,7 @@ def seal(decl: dict) -> dict:
|
|
|
77
82
|
return d
|
|
78
83
|
|
|
79
84
|
|
|
85
|
+
# 生效条件:p=theory_file(path) 后,os.path.exists(p) 为假返回 None;open/json.load 抛 OSError 或 ValueError 也返回 None;否则 d 是 dict 时返回 d,不是 dict 返回 None。
|
|
80
86
|
def load(path: str = None):
|
|
81
87
|
p = theory_file(path)
|
|
82
88
|
if not os.path.exists(p):
|
|
@@ -89,6 +95,7 @@ def load(path: str = None):
|
|
|
89
95
|
return d if isinstance(d, dict) else None
|
|
90
96
|
|
|
91
97
|
|
|
98
|
+
# 生效条件:p=theory_file(path),先 os.makedirs(os.path.dirname(p) or "."),把 decl 以 ensure_ascii=False, indent=1 写入 p+".tmp" 再 os.replace 到 p,os.chmod(p, 0o600) 抛 OSError 时忽略,最终返回 p。
|
|
92
99
|
def save(decl: dict, path: str = None) -> str:
|
|
93
100
|
p = theory_file(path)
|
|
94
101
|
os.makedirs(os.path.dirname(p) or ".", exist_ok=True)
|
|
@@ -107,6 +114,7 @@ def save(decl: dict, path: str = None) -> str:
|
|
|
107
114
|
# 声明与校验
|
|
108
115
|
# --------------------------------------------------------------------------
|
|
109
116
|
|
|
117
|
+
# 生效条件:v=(version or PROTOCOL_VERSION).strip(),v 为空串时抛 TheoryError;accepted 为假值(None/空)时 acc=[v],否则 list(accepted) 并剔除 strip 后为空的项,v 不在 acc 时 insert(0, v);declared_by 假值回落 "designer",declared_at=float(now if now is not None else time.time()),返回含 SCHEMA 的 dict。
|
|
110
118
|
def make_declaration(version: str = None, accepted=None,
|
|
111
119
|
declared_by: str = "designer", now: float = None) -> dict:
|
|
112
120
|
"""构造(未封缄的)版本声明。accepted 默认只含自身版本。"""
|
|
@@ -122,6 +130,7 @@ def make_declaration(version: str = None, accepted=None,
|
|
|
122
130
|
"declared_at": float(now if now is not None else time.time())}
|
|
123
131
|
|
|
124
132
|
|
|
133
|
+
# 生效条件:d=decl if decl is not None else load(theory_file(path));d 为假时返回 theory_ok=True、auto=True、version=PROTOCOL_VERSION 的状态;d.get("schema")!=SCHEMA、hmac.compare_digest(str(d.get("declaration_hash") or ""), declaration_hash(_payload(d))) 不通过、v=d.get("theory_version") 为假、或 v 不在 acc=[str(x) for x in (d.get("accepted_versions") or [])] 时各自返回同一个 base 且 theory_ok=False,其余情况 base 更新为 theory_ok=True、reason="ok"。
|
|
125
134
|
def check(path: str = None, decl: dict = None) -> dict:
|
|
126
135
|
"""校验版本声明 → 状态字典(永不抛异常,供启动路径安全调用)。
|
|
127
136
|
|
|
@@ -164,6 +173,7 @@ def check(path: str = None, decl: dict = None) -> dict:
|
|
|
164
173
|
return base
|
|
165
174
|
|
|
166
175
|
|
|
176
|
+
# 生效条件:p=theory_file(path),仅当 load(p) 为 None 时尝试 declare(PROTOCOL_VERSION, actor="system:autoinit", path=p, append_audit=False) 并吞掉 TheoryError/OSError,最终返回 check(p)。
|
|
167
177
|
def ensure(path: str = None) -> dict:
|
|
168
178
|
"""启动路径:无声明则落盘默认声明(声明常态化),再返回校验状态。
|
|
169
179
|
|
|
@@ -179,6 +189,7 @@ def ensure(path: str = None) -> dict:
|
|
|
179
189
|
return check(p)
|
|
180
190
|
|
|
181
191
|
|
|
192
|
+
# 生效条件:version=None/空串时在 make_declaration 内回落 PROTOCOL_VERSION(仅回落结果 strip 后仍为空才抛 TheoryError),seal 后若 append_audit 为真值则把 (old or {}).get("audit") 或 [] 追加一条(仅 old 为真时追加)写入 decl["audit"],save(decl, path) 后返回 {'ok': True, 'declaration': decl, 'status': check(path)}。
|
|
182
193
|
def declare(version: str = None, accepted=None, actor: str = "designer",
|
|
183
194
|
path: str = None, append_audit: bool = True) -> dict:
|
|
184
195
|
"""写入版本声明(版本更新权的唯一物理落点)。"""
|
|
@@ -195,11 +206,13 @@ def declare(version: str = None, accepted=None, actor: str = "designer",
|
|
|
195
206
|
return {"ok": True, "declaration": decl, "status": check(path)}
|
|
196
207
|
|
|
197
208
|
|
|
209
|
+
# 生效条件:以 path 调 load、check、theory_file,返回 {"declaration": load(path), "status": check(path), "theory_file": theory_file(path)}。
|
|
198
210
|
def show(path: str = None) -> dict:
|
|
199
211
|
return {"declaration": load(path), "status": check(path),
|
|
200
212
|
"theory_file": theory_file(path)}
|
|
201
213
|
|
|
202
214
|
|
|
215
|
+
# 生效条件:返回含模块常量 SCHEMA、PROTOCOL_VERSION、list(SEALED_FIELDS)、list(ESCAPE_OPS) 及 theory_file() 与 THEORY_ENV/THEORY_FILE_ENV 的自描述 dict。
|
|
203
216
|
def catalog() -> dict:
|
|
204
217
|
"""版本层自描述(供 whoami / service_info 暴露)。"""
|
|
205
218
|
return {"schema": SCHEMA, "theory_file": theory_file(),
|
|
@@ -213,10 +226,12 @@ def catalog() -> dict:
|
|
|
213
226
|
# CLI
|
|
214
227
|
# --------------------------------------------------------------------------
|
|
215
228
|
|
|
229
|
+
# 生效条件:把 json.dumps(obj, ensure_ascii=False, indent=1)+"\n" 写入 sys.stdout,无返回值。
|
|
216
230
|
def _print(obj):
|
|
217
231
|
sys.stdout.write(json.dumps(obj, ensure_ascii=False, indent=1) + "\n")
|
|
218
232
|
|
|
219
233
|
|
|
234
|
+
# 生效条件:argv 交给 ap.parse_args(argv 为 None 时 argparse 读 sys.argv),子解析器 required=True 缺 cmd 时由 argparse 报错退出;a.cmd 为 declare/check/ensure/show/catalog 之一且未抛 TheoryError 时打印并返回 0,抛出 TheoryError 时写 stderr 返回 2。
|
|
220
235
|
def main(argv=None):
|
|
221
236
|
ap = argparse.ArgumentParser(
|
|
222
237
|
prog="python -m md_cg.theory",
|
|
@@ -256,4 +271,4 @@ def main(argv=None):
|
|
|
256
271
|
|
|
257
272
|
|
|
258
273
|
if __name__ == "__main__":
|
|
259
|
-
sys.exit(main())
|
|
274
|
+
sys.exit(main())
|
package/md_cg/tokens.py
CHANGED
|
@@ -79,7 +79,15 @@ ALL_OPS = ("help", "info", "route", "read", "write", "goal", "task", "recent", "
|
|
|
79
79
|
# —— 对话记录→六要素候选→**编外复核**→落库;裁定 A:编译者不得自证
|
|
80
80
|
# (E041 机械拒绝 verifier == compiled_by)。复核通道优先蜂巢
|
|
81
81
|
# reflect/verify 单元,不可用则提示配置或降级 harness 端子代理。
|
|
82
|
-
|
|
82
|
+
# P4 新增(可验证记忆单元,2026-09-19):
|
|
83
|
+
# status 验证态 / 依赖 / 双时间轴 / 履历查询(只读)
|
|
84
|
+
# —— 「它还成不成立」的读面;真源 md_cg/trust.py
|
|
85
|
+
# P4 新增(三元组反查原语,阶段二 4.2,2026-09-20):
|
|
86
|
+
# edges 按任意端 / 谓词 / 时间 + 排序分页聚合反查派生边(只读)
|
|
87
|
+
# —— 「这条记忆从哪来 / 谁由它派生」的读面;真源
|
|
88
|
+
# md_cg/provenance.py(find_edges)。**只读 op**:不含任何
|
|
89
|
+
# 写入 path,故读面角色一律放行(与 status 同档)
|
|
90
|
+
"ccg", "status", "edges")
|
|
83
91
|
|
|
84
92
|
|
|
85
93
|
class TokenError(Exception):
|
|
@@ -115,7 +123,8 @@ ROLE_SPECS = OrderedDict([
|
|
|
115
123
|
# task=结构层任务台账(工程做到哪一步/结果是什么)——记录单元本职:
|
|
116
124
|
# 保存过程与结果;「不得自证」由 done 时的结果必填闸承接
|
|
117
125
|
"ops_allow": ["info", "route", "read", "write", "goal", "task", "recent",
|
|
118
|
-
"session", "ingest", "maintain", "insight", "ccg"
|
|
126
|
+
"session", "ingest", "maintain", "insight", "ccg", "status",
|
|
127
|
+
"edges"],
|
|
119
128
|
"delegable": False,
|
|
120
129
|
"forbidden": ["self/anchor 层", "private/secret 密级", "裁决与删除"],
|
|
121
130
|
}),
|
|
@@ -128,7 +137,7 @@ ROLE_SPECS = OrderedDict([
|
|
|
128
137
|
# maintain=反思后的前馈/模式分离候选(apply 类改写走 require_admin)
|
|
129
138
|
# insight=发现差异/新路径:开窗 window + 情景重构 reconstruct + 盲区学习 learn
|
|
130
139
|
"ops_allow": ["info", "route", "read", "write", "recent", "metacognition",
|
|
131
|
-
"session", "maintain", "insight"],
|
|
140
|
+
"session", "maintain", "insight", "status", "edges"],
|
|
132
141
|
"delegable": False,
|
|
133
142
|
"forbidden": ["knowledge/self/anchor 层", "private/secret 密级", "裁决与删除"],
|
|
134
143
|
}),
|
|
@@ -137,7 +146,8 @@ ROLE_SPECS = OrderedDict([
|
|
|
137
146
|
"duty": "判断规则、执行结果和结构是否有效;只写验证证据与负记忆",
|
|
138
147
|
"can_write": True, "can_admin": False, "clearance_cap": "internal",
|
|
139
148
|
"layers_allow": ["rejected", "contextual"],
|
|
140
|
-
"ops_allow": ["info", "route", "read", "write", "verify", "insight"
|
|
149
|
+
"ops_allow": ["info", "route", "read", "write", "verify", "insight",
|
|
150
|
+
"status", "edges"],
|
|
141
151
|
"delegable": False,
|
|
142
152
|
"forbidden": ["knowledge/self/anchor 层(不得改被验证内容)",
|
|
143
153
|
"private/secret 密级", "裁决与删除"],
|
|
@@ -150,7 +160,7 @@ ROLE_SPECS = OrderedDict([
|
|
|
150
160
|
# session 仅开放只读 recall;note/compact 在分发层按 can_write 拦截
|
|
151
161
|
# insight 仅开放只读呈现(list/report/outlook/reconstruct);写入被 can_write 拦截
|
|
152
162
|
"ops_allow": ["info", "route", "read", "recent", "whitebox", "session",
|
|
153
|
-
"insight"],
|
|
163
|
+
"insight", "status", "edges"],
|
|
154
164
|
"delegable": False,
|
|
155
165
|
"forbidden": ["全部写入", "private/secret 密级", "管理操作"],
|
|
156
166
|
}),
|
|
@@ -164,7 +174,7 @@ ROLE_SPECS = OrderedDict([
|
|
|
164
174
|
# insight=整体结构洞察 outlook(趋势/盲区/建议)+ 条件层报告 report
|
|
165
175
|
"ops_allow": ["info", "read", "write", "sustain", "scrub", "evolution",
|
|
166
176
|
"self_state", "metacognition", "link", "session",
|
|
167
|
-
"maintain", "insight"],
|
|
177
|
+
"maintain", "insight", "status", "edges"],
|
|
168
178
|
"delegable": False,
|
|
169
179
|
"forbidden": ["knowledge/anchor 层", "private/secret 密级", "裁决与删除"],
|
|
170
180
|
}),
|
|
@@ -173,7 +183,8 @@ ROLE_SPECS = OrderedDict([
|
|
|
173
183
|
"duty": "无令牌时的降级身份:只读、最低密级",
|
|
174
184
|
"can_write": False, "can_admin": False, "clearance_cap": "internal",
|
|
175
185
|
"layers_allow": [],
|
|
176
|
-
"ops_allow": ["info", "route", "read", "recent", "whitebox"
|
|
186
|
+
"ops_allow": ["info", "route", "read", "recent", "whitebox", "status",
|
|
187
|
+
"edges"],
|
|
177
188
|
"delegable": False,
|
|
178
189
|
"forbidden": ["全部写入", "private/secret 密级", "管理操作"],
|
|
179
190
|
}),
|
|
@@ -216,11 +227,13 @@ ORCH_OPS_ALLOW = ("route", "read", "write", "review", "recent", "consistency")
|
|
|
216
227
|
ORCH_LAYERS_ALLOW = tuple(l for l in ALL_LAYERS if l not in CORE_LAYERS)
|
|
217
228
|
|
|
218
229
|
|
|
230
|
+
# 生效条件:role 为假值(None/空串)时按 "" 处理,经 strip().lower() 得 r,r 命中 ROLE_ALIASES 键时返回别名,否则返回 r 本身。
|
|
219
231
|
def normalize_role(role: str) -> str:
|
|
220
232
|
r = (role or "").strip().lower()
|
|
221
233
|
return ROLE_ALIASES.get(r, r)
|
|
222
234
|
|
|
223
235
|
|
|
236
|
+
# 生效条件:normalize_role(role) 得 r,r 不在 ROLE_SPECS 中即抛 TokenError,否则返回 dict(ROLE_SPECS[r]) 的浅拷贝。
|
|
224
237
|
def role_spec(role: str):
|
|
225
238
|
r = normalize_role(role)
|
|
226
239
|
if r not in ROLE_SPECS:
|
|
@@ -228,6 +241,7 @@ def role_spec(role: str):
|
|
|
228
241
|
return dict(ROLE_SPECS[r])
|
|
229
242
|
|
|
230
243
|
|
|
244
|
+
# 生效条件:无 required 形参,调用即返回含 SCHEMA、token_file()、CORE_PRIVATE_SENSITIVITIES/CORE_LAYERS/ALL_LAYERS/POSITION_ROLES/DELEGABLE_ROLES/ROLE_ALIASES/ROLE_SPECS 各自 list/dict 拷贝的字典。
|
|
231
245
|
def catalog():
|
|
232
246
|
"""角色职责矩阵(供 whoami / service_info / 文档自描述)。"""
|
|
233
247
|
return {"schema": SCHEMA, "token_file": token_file(),
|
|
@@ -243,10 +257,12 @@ def catalog():
|
|
|
243
257
|
# 存储(仓库外,0600)
|
|
244
258
|
# --------------------------------------------------------------------------
|
|
245
259
|
|
|
260
|
+
# 生效条件:path 为真值时返回 path,否则回落 os.environ.get(TOKEN_FILE_ENV)(取到空串同为假值),仍为假值时返回 DEFAULT_TOKEN_FILE。
|
|
246
261
|
def token_file(path: str = None) -> str:
|
|
247
262
|
return path or os.environ.get(TOKEN_FILE_ENV) or DEFAULT_TOKEN_FILE
|
|
248
263
|
|
|
249
264
|
|
|
265
|
+
# 生效条件:p=token_file(path) 的 os.path.exists(p) 为真且 json.load 得到 dict 时,setdefault("tokens", {}) 后返回该 dict;路径不存在、解析结果非 dict 或抛 OSError/ValueError 时返回 {"schema": SCHEMA, "tokens": {}}。
|
|
250
266
|
def _load(path: str = None) -> dict:
|
|
251
267
|
p = token_file(path)
|
|
252
268
|
if os.path.exists(p):
|
|
@@ -261,6 +277,7 @@ def _load(path: str = None) -> dict:
|
|
|
261
277
|
return {"schema": SCHEMA, "tokens": {}}
|
|
262
278
|
|
|
263
279
|
|
|
280
|
+
# 生效条件:以 data 为内容、p=token_file(path) 为目标,先对 os.path.dirname(p) or "." 做 makedirs(exist_ok=True),写 p+".tmp" 后 os.replace 覆盖 p,再尝试 chmod 0600(仅吞 OSError)。
|
|
264
281
|
def _save(data: dict, path: str = None):
|
|
265
282
|
p = token_file(path)
|
|
266
283
|
os.makedirs(os.path.dirname(p) or ".", exist_ok=True)
|
|
@@ -274,14 +291,17 @@ def _save(data: dict, path: str = None):
|
|
|
274
291
|
pass
|
|
275
292
|
|
|
276
293
|
|
|
294
|
+
# 生效条件:secret 经 encode("utf-8") 后取 sha256 的 hexdigest;secret 非 str 时按其 encode 失败抛出。
|
|
277
295
|
def _hash(secret: str) -> str:
|
|
278
296
|
return hashlib.sha256(secret.encode("utf-8")).hexdigest()
|
|
279
297
|
|
|
280
298
|
|
|
299
|
+
# 生效条件:_rank(want) <= _rank(cap) 时返回 want,否则返回 cap。
|
|
281
300
|
def _clamp_level(want: str, cap: str) -> str:
|
|
282
301
|
return want if _rank(want) <= _rank(cap) else cap
|
|
283
302
|
|
|
284
303
|
|
|
304
|
+
# 生效条件:allow 为 None 时按 parent_allow 是否为 None 返回 None 或 list(parent_allow);否则 parent_allow 为 None 或含 "*" 时返回 list(allow);否则 allow 含 "*" 时返回 list(parent_allow);否则返回 [x for x in allow if x in parent_allow]。
|
|
285
305
|
def _narrow(allow, parent_allow):
|
|
286
306
|
"""求交:子权限只能收窄。父为 None(不限制)时取子;子为 None 时取父。"""
|
|
287
307
|
if allow is None:
|
|
@@ -293,10 +313,12 @@ def _narrow(allow, parent_allow):
|
|
|
293
313
|
return [x for x in allow if x in parent_allow]
|
|
294
314
|
|
|
295
315
|
|
|
316
|
+
# 生效条件:role/token_id/secret 即使为假值也照拼,返回 f"{PREFIX}.{role}.{token_id}.{secret}"。
|
|
296
317
|
def make_token(role: str, token_id: str, secret: str) -> str:
|
|
297
318
|
return f"{PREFIX}.{role}.{token_id}.{secret}"
|
|
298
319
|
|
|
299
320
|
|
|
321
|
+
# 生效条件:token 为假值时按 "" 处理,split(".") 后长度不为 4 或 parts[0] != PREFIX 即抛 TokenError;长度与前缀合规后 role/token_id/secret 任一为空再抛 TokenError;否则返回 (role.lower(), token_id, secret)。
|
|
300
322
|
def parse_token(token: str):
|
|
301
323
|
parts = (token or "").strip().split(".")
|
|
302
324
|
if len(parts) != 4 or parts[0] != PREFIX:
|
|
@@ -311,6 +333,7 @@ def parse_token(token: str):
|
|
|
311
333
|
# 签发 / 校验 / 派生 / 吊销
|
|
312
334
|
# --------------------------------------------------------------------------
|
|
313
335
|
|
|
336
|
+
# 生效条件:role 经 normalize_role+role_spec(未知角色抛 TokenError),clearance 为假值时取 spec["clearance_cap"] 再经 _clamp_level 收窄到该 cap,layers_allow/ops_allow 经 _narrow 与 spec 默认求交,actor 为假值时取 role,delegable is None 时取 spec["delegable"]、否则按所传值取 bool,ttl 为真值时 expires_at=now+float(ttl)、为假值(None/0)时 None,记录写入 token_file(path) 后返回含明文 token 的字典。
|
|
314
337
|
def issue(role: str, actor: str = None, clearance: str = None,
|
|
315
338
|
tenant: str = "default", ttl: float = None, label: str = "",
|
|
316
339
|
issued_by: str = "root", parent: str = None, delegable: bool = None,
|
|
@@ -345,6 +368,7 @@ def issue(role: str, actor: str = None, clearance: str = None,
|
|
|
345
368
|
"expires_at": rec["expires_at"], "token_file": token_file(path)}
|
|
346
369
|
|
|
347
370
|
|
|
371
|
+
# 生效条件:token 先经 parse_token(格式非法即抛 TokenError),其后 _load(path) 的 tokens 中该 token_id 无记录、rec 的 role 与解析出的 role 不等、revoked_at 为真、hash 与 _hash(secret) 经 hmac.compare_digest 不等、expires_at 为真且小于当前时间中任一成立即抛 TokenError;否则返回 Principal,tenant=tenant or rec.get("tenant") or "default"、actor=rec.get("actor") or role、clearance=rec.get("clearance") or "internal"、can_write/can_admin 取对应 rec 值的 bool。
|
|
348
372
|
def verify_token(token: str, tenant: str = None, path: str = None) -> Principal:
|
|
349
373
|
"""校验令牌 → Principal。任何异常都抛 TokenError(fail-closed)。"""
|
|
350
374
|
role, token_id, secret = parse_token(token)
|
|
@@ -371,6 +395,7 @@ def verify_token(token: str, tenant: str = None, path: str = None) -> Principal:
|
|
|
371
395
|
ops_allow=rec.get("ops_allow"), auth_mode="token")
|
|
372
396
|
|
|
373
397
|
|
|
398
|
+
# 生效条件:parent_token 经 verify_token(parent_token, path=path) 成功且父记录 delegable 为真才继续,否则抛 TokenError;role 经 normalize_role+role_spec,clearance 为假值时取 spec["clearance_cap"] 再 _clamp_level,若仍高于 parent.clearance 则降为 parent.clearance 并向 clamped 追加 "clearance";layers/ops 先与 spec 求交再与父记录求交;can_write/can_admin 取 spec 与父对应值的与;ttl 为真值时 expires_at=now+float(ttl)、为假值(None/0)时沿用父记录 expires_at;新记录 delegable 恒 False、parent/issued_by 为 parent.token_id,返回含明文 token 与 clamped 的字典。
|
|
374
399
|
def derive(parent_token: str, role: str, actor: str = None, ttl: float = None,
|
|
375
400
|
label: str = "", path: str = None, clearance: str = None,
|
|
376
401
|
layers_allow=None, ops_allow=None):
|
|
@@ -414,6 +439,7 @@ def derive(parent_token: str, role: str, actor: str = None, ttl: float = None,
|
|
|
414
439
|
"expires_at": rec["expires_at"]}
|
|
415
440
|
|
|
416
441
|
|
|
442
|
+
# 生效条件:normalize_role(unit) 结果不在 POSITION_ROLES 时抛 TokenError;否则返回 Principal:unit/role=u、can_admin 恒 False、clearance=_clamp_level(spec["clearance_cap"], p.clearance)、can_write=bool(spec["can_write"]) and bool(p.can_write)、layers_allow/ops_allow=_narrow(spec 对应值, p 对应值),tenant/actor/session/harness/token_id/parent/expires_at/auth_mode/theory 等沿用 p。
|
|
417
443
|
def narrowed_principal(p: Principal, unit: str) -> Principal:
|
|
418
444
|
"""按「单元」收窄 principal 权限(**请求级**身份,只能变小不能变大)。
|
|
419
445
|
|
|
@@ -450,6 +476,7 @@ def narrowed_principal(p: Principal, unit: str) -> Principal:
|
|
|
450
476
|
theory_ok=p.theory_ok, theory_version=p.theory_version)
|
|
451
477
|
|
|
452
478
|
|
|
479
|
+
# 生效条件:token_id 在 _load(path) 的 tokens 中无记录时抛 TokenError;有记录则将其 revoked_at 置为 time.time() 并 _save,再对 tokens 中 parent 等于 token_id 的每个子令牌递归 revoke(c, path),返回 {"ok": True, "token_id": token_id, "revoked_children": children}。
|
|
453
480
|
def revoke(token_id: str, path: str = None):
|
|
454
481
|
data = _load(path)
|
|
455
482
|
rec = (data.get("tokens") or {}).get(token_id)
|
|
@@ -464,6 +491,7 @@ def revoke(token_id: str, path: str = None):
|
|
|
464
491
|
return {"ok": True, "token_id": token_id, "revoked_children": children}
|
|
465
492
|
|
|
466
493
|
|
|
494
|
+
# 生效条件:从 _load(path)(path 为 None 时取默认令牌文件)的 tokens 取值;条目的 revoked_at 为真且 include_revoked 为假时跳过;返回不含密钥材料与摘要的清单;
|
|
467
495
|
def list_tokens(path: str = None, include_revoked: bool = False):
|
|
468
496
|
"""令牌清单(不含密钥材料与摘要)。"""
|
|
469
497
|
out = []
|
|
@@ -488,10 +516,12 @@ def list_tokens(path: str = None, include_revoked: bool = False):
|
|
|
488
516
|
# CLI:签发 / 派生 / 清单 / 吊销 / 角色矩阵
|
|
489
517
|
# --------------------------------------------------------------------------
|
|
490
518
|
|
|
519
|
+
# 生效条件:obj 一律经 json.dumps(obj, ensure_ascii=False, indent=1) 加换行写入 sys.stdout;obj 不可 JSON 序列化时抛出 TypeError。
|
|
491
520
|
def _print(obj):
|
|
492
521
|
sys.stdout.write(json.dumps(obj, ensure_ascii=False, indent=1) + "\n")
|
|
493
522
|
|
|
494
523
|
|
|
524
|
+
# 生效条件:v 为假值(None/空串)时返回 None;否则按 "," 切分、strip 后丢弃空项,结果为空列表时同样返回 None。
|
|
495
525
|
def _csv_list(v):
|
|
496
526
|
"""CLI 的逗号分隔白名单 → list;空值返回 None(= 不额外收窄)。
|
|
497
527
|
|
|
@@ -502,6 +532,7 @@ def _csv_list(v):
|
|
|
502
532
|
return [x.strip() for x in str(v).split(",") if x.strip()] or None
|
|
503
533
|
|
|
504
534
|
|
|
535
|
+
# 生效条件:path 经 os.path.abspath 得 p,其 dirname 非空时 makedirs(exist_ok=True),text 原样写入 p+".tmp" 后 os.replace 覆盖 p,再尝试 chmod 0600(仅吞 OSError)。
|
|
505
536
|
def _write_secret(path: str, text: str) -> None:
|
|
506
537
|
"""把令牌明文写入文件(0600,原子替换)——供 HIVE_ORCH_TOKEN_FILE 读取。"""
|
|
507
538
|
p = os.path.abspath(path)
|
|
@@ -518,6 +549,7 @@ def _write_secret(path: str, text: str) -> None:
|
|
|
518
549
|
pass
|
|
519
550
|
|
|
520
551
|
|
|
552
|
+
# 生效条件:argv 为 None 时取 sys.argv[1:];经 argparse 解析(--token-file 与必填子命令 issue/verify/revoke/list/roles 等);参数非法时经 argparse 退出,写盘失败抛 OSError;
|
|
521
553
|
def main(argv=None):
|
|
522
554
|
ap = argparse.ArgumentParser(
|
|
523
555
|
prog="python -m md_cg.tokens",
|
|
@@ -629,4 +661,4 @@ def main(argv=None):
|
|
|
629
661
|
|
|
630
662
|
|
|
631
663
|
if __name__ == "__main__":
|
|
632
|
-
sys.exit(main())
|
|
664
|
+
sys.exit(main())
|
package/md_cg/tool_face.py
CHANGED
|
@@ -38,7 +38,8 @@ FULL_ENV = "MDCG_TOOL_FACE"
|
|
|
38
38
|
CG_HELP_HINT = '完整 op/参数语义:cg(op="help", query="<op 名 | 参数名 | 工具名>")'
|
|
39
39
|
|
|
40
40
|
TOOL_BRIEFS = {
|
|
41
|
-
"cg": "灵枢认知图唯一入口(
|
|
41
|
+
"cg": "灵枢认知图唯一入口(34 op:写/读/审/护/演化/元认知/预测/白箱/验证态 status/"
|
|
42
|
+
"三元组反查 edges…)。" + CG_HELP_HINT,
|
|
42
43
|
"stg": "语义时空图 4 op:relation|timeline|anchors|consistency。" + CG_HELP_HINT,
|
|
43
44
|
"mdcg_remember": "写入记忆节点(gated=true 走主动遗忘闸门;正文建议含 CCG 五要素)。",
|
|
44
45
|
"mdcg_recall": "按 token 预算召回记忆包(RRF 多路融合,可开模糊/条件语义路与查询扩展)。",
|
|
@@ -93,6 +94,7 @@ GENERIC_PARAMS = frozenset({
|
|
|
93
94
|
})
|
|
94
95
|
|
|
95
96
|
|
|
97
|
+
# 生效条件:desc 为 None/空串等假值时按 `desc or ""` 回落空串、`_OP_SEC_RE.finditer` 无匹配而返回空 dict;否则以每个匹配的组 1 为键、组 2 去首尾空白并去掉结尾「;/;」后为值填入并返回。
|
|
96
98
|
def op_sections(desc):
|
|
97
99
|
"""从工具 description 里切出 {op: 段落}(机械切分,无损)。"""
|
|
98
100
|
out = {}
|
|
@@ -101,6 +103,7 @@ def op_sections(desc):
|
|
|
101
103
|
return out
|
|
102
104
|
|
|
103
105
|
|
|
106
|
+
# 生效条件:tools 为空时可迭代但无元素、返回空 set;否则遍历每个工具,并入其 `t.get("description") or ""` 经 op_sections 得到的键,以及其 `(t.get("inputSchema") or {}).get("properties") or {}` 中名为 op/action 的属性 description(缺省 "")里 `_ENUM_RE` 匹配串按 "|" 拆出的词,返回并集 vocab。
|
|
104
107
|
def op_vocab(tools):
|
|
105
108
|
"""全量 op 词汇表(用于判定「这是 op 专属参数的描述」)。"""
|
|
106
109
|
vocab = set()
|
|
@@ -115,11 +118,13 @@ def op_vocab(tools):
|
|
|
115
118
|
return vocab
|
|
116
119
|
|
|
117
120
|
|
|
121
|
+
# 生效条件:head=(desc or "")[:prefix_len](desc 假值即空串、prefix_len 默认 18),当 vocab 中存在长度 ≥3 且作为子串出现在 head 中的元素时返回 True,否则(含 vocab 为空)返回 False。
|
|
118
122
|
def _is_op_specific(desc, vocab, prefix_len=18):
|
|
119
123
|
head = (desc or "")[:prefix_len]
|
|
120
124
|
return any(len(o) >= 3 and o in head for o in vocab)
|
|
121
125
|
|
|
122
126
|
|
|
127
|
+
# 生效条件:desc 为假值返回 None;否则空白归一化后按 _ENUM_RE 结果分派——恰 1 组返回该组、多于 1 组返回 ENUM_PLACEHOLDER、0 组时若 op_specific 为真则 d 长度 ≤OP_SPECIFIC_LIMIT 原样返回否则 None,若 op_specific 为假则 d 长度 >GENERIC_LIMIT 时返回 d[:GENERIC_LIMIT].rstrip()+"…"、否则原样返回 d。
|
|
123
128
|
def short_desc(desc, vocab, op_specific=False):
|
|
124
129
|
"""投影一条参数描述。
|
|
125
130
|
|
|
@@ -142,10 +147,12 @@ def short_desc(desc, vocab, op_specific=False):
|
|
|
142
147
|
return d
|
|
143
148
|
|
|
144
149
|
|
|
150
|
+
# 生效条件:tool 的 "inputSchema" 键存在且值为真时原样返回该值;键缺失或值为 None/{} 等假值时按 `or {}` 回落返回空 dict。
|
|
145
151
|
def _schema_of(tool):
|
|
146
152
|
return tool.get("inputSchema") or {}
|
|
147
153
|
|
|
148
154
|
|
|
155
|
+
# 生效条件:tool 须含 "name" 键(缺失即 KeyError),desc=`tool.get("description") or ""`、isch=`_schema_of(tool)`;逐条属性去掉 "_req" 键后,若其 description 为真则按「key 在 GENERIC_PARAMS 或 `not _is_op_specific(d, vocab)`」判为 generic,再经 short_desc 投影——非空则替换 description、空则弹出该 description 键;返回 name 与 `TOOL_BRIEFS.get(name) or _first_sentence(desc,140)` 的 description,inputSchema 的 type 取 `isch.get("type") or "object"`,仅当 required 非空时才带 "required"。
|
|
149
156
|
def slim_tool(tool, vocab):
|
|
150
157
|
"""投影单个工具(不修改入参)。"""
|
|
151
158
|
name = tool["name"]
|
|
@@ -172,6 +179,7 @@ def slim_tool(tool, vocab):
|
|
|
172
179
|
"inputSchema": out_schema}
|
|
173
180
|
|
|
174
181
|
|
|
182
|
+
# 生效条件:d=折叠 (desc or "") 空白,按 "。"、";"、";" 顺序取各字符在 d 中 `find` 的首次出现位置 i,遇到首个满足 0<i<=limit 者即返回 d[:i+1];三个字符的首次位置均不满足时,len(d)>limit 返回 d[:limit].rstrip()+"…",否则原样返回 d(limit=0 时无 i 满足,d 非空则返回 "…")。
|
|
175
183
|
def _first_sentence(desc, limit):
|
|
176
184
|
d = " ".join((desc or "").split())
|
|
177
185
|
for ch in ("。", ";", ";"):
|
|
@@ -181,6 +189,7 @@ def _first_sentence(desc, limit):
|
|
|
181
189
|
return (d[:limit].rstrip() + "…") if len(d) > limit else d
|
|
182
190
|
|
|
183
191
|
|
|
192
|
+
# 生效条件:环境变量 FULL_ENV 取值 `(os.environ.get(FULL_ENV) or "").strip().lower()=="full"`(未设或设为空串均不成立)时原样返回入参 tools;否则以 tools 算出 vocab 并返回 `[slim_tool(t, vocab) for t in tools]`(tools 为空则返回空列表)。
|
|
184
193
|
def slim_tools(tools):
|
|
185
194
|
"""投影整个工具面。MDCG_TOOL_FACE=full 时原样返回(调试/兼容)。"""
|
|
186
195
|
if (os.environ.get(FULL_ENV) or "").strip().lower() == "full":
|
|
@@ -192,6 +201,7 @@ def slim_tools(tools):
|
|
|
192
201
|
# --------------------------------------------------------------------------
|
|
193
202
|
# 按需披露:cg(op=help, query=…)
|
|
194
203
|
# --------------------------------------------------------------------------
|
|
204
|
+
# 生效条件:取 `_schema_of(tool)` 的 properties,仅保留 description(缺省 "")满足 `_is_op_specific(desc, {op})` 的属性,每项记为 name/type/required(k in required)/desc,返回该列表的 `out[:limit]` 切片(limit 默认 40,limit=0 得空列表,负值从尾部削减)。
|
|
195
205
|
def _params_for(tool, op, limit=40):
|
|
196
206
|
isch = _schema_of(tool)
|
|
197
207
|
props = isch.get("properties") or {}
|
|
@@ -204,6 +214,7 @@ def _params_for(tool, op, limit=40):
|
|
|
204
214
|
return out[:limit]
|
|
205
215
|
|
|
206
216
|
|
|
217
|
+
# 生效条件:tools 用于建 `by_name={t["name"]: t}`(某工具缺 "name" 即 KeyError),q=(query or "").strip()——q 为空串返回 {"ok":True,"tool_index":[每工具 name 与 `TOOL_BRIEFS.get(name) or _first_sentence(desc,120)`],"usage":...};q 等于某工具名时返回该工具的 description/ops(op_sections 键排序)/params/required;否则逐工具收集「q 出现在 op 段落键中」与「pname==q 或 len(q)>=4 且 pname 以 q 开头」两类命中(不互斥、可同时追加),全部工具遍历后无命中返回 {"ok":False,"error",...,"hint",...},有命中返回 {"ok":True,"query":q,"hits":hits[:max(1, limit)]}(limit≤1 时按 1 条截取)。
|
|
207
218
|
def help_text(tools, query=None, limit=40):
|
|
208
219
|
"""从**完整真源**按需渲染说明——被投影掉的原文在此无损取回。"""
|
|
209
220
|
q = (query or "").strip()
|
|
@@ -247,4 +258,4 @@ def help_text(tools, query=None, limit=40):
|
|
|
247
258
|
if not hits:
|
|
248
259
|
return {"ok": False, "error": f"未找到 op / 参数 / 工具:{q}",
|
|
249
260
|
"hint": "cg(op=help) 不带 query 可列出全部工具与一行职责"}
|
|
250
|
-
return {"ok": True, "query": q, "hits": hits[:max(1, limit)]}
|
|
261
|
+
return {"ok": True, "query": q, "hits": hits[:max(1, limit)]}
|