@furongjun1999/dsh-memory 0.7.2 → 0.7.4
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 +57 -47
- package/codebuddy/CODEBUDDY.md +6 -6
- package/docs/README.md +148 -144
- package/docs/discipline/harnesses.yaml +16 -8
- package/docs/discipline/templates/zcode-user.md.tmpl +49 -0
- package/docs/eval/AGI/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_/345/205/255/345/256/266/350/256/260/345/277/206/347/263/273/347/273/237/345/220/214/345/260/272/345/256/236/346/265/213_v3.1.md +340 -0
- package/docs/eval/N225_/347/264/242/345/274/225/346/227/245/345/277/227/351/235/236/345/257/271/350/261/241/350/243/205/350/275/275/351/235/242/347/261/273/345/236/213/351/227/270_v1.0.md +2 -2
- package/docs/eval/cons200_/345/206/262/347/252/201/346/243/200/346/265/213/351/200/211/351/235/242_/345/256/236/346/226/275/350/256/260/345/275/225_v1.0.md +1 -1
- package/docs/eval/cons200_/345/206/262/347/252/201/346/243/200/346/265/213/351/200/211/351/235/242_/345/256/236/346/226/275/350/256/260/345/275/225_v1.1.md +1 -1
- package/docs/eval/issue43_/351/273/230/350/256/244/347/255/226/347/225/245/344/270/216/351/224/256/347/261/273/345/236/213/351/227/270_v1.1.md +1 -1
- package/docs/eval/issue43_/351/273/230/350/256/244/347/255/226/347/225/245/345/212/240/350/275/275/344/270/216/345/207/255/346/215/256/346/230/216/346/226/207/351/230/237/345/210/227_v1.0.md +2 -2
- package/docs/eval/issue50_/345/205/203/346/225/260/346/215/256/351/200/217/344/274/240/344/270/216/345/205/234/345/272/225_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +5 -5
- package/docs/eval/issue50_/345/215/212/351/207/215/345/244/215/345/276/205/345/256/232/345/244/215/346/240/270_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +9 -9
- package/docs/eval/issue50_/345/276/205/345/256/232/345/244/215/346/240/270/345/205/245/351/230/237_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +4 -4
- package/docs/eval/issue50_/351/207/215/350/246/201/345/272/246/345/220/214/346/272/220/344/270/216/344/277/235/346/212/244/350/257/255/344/271/211_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +1 -1
- package/docs/eval/issue51_/344/270/200/351/224/256/345/256/211/350/243/205/345/244/261/350/264/245_/345/275/222/345/261/236/345/210/244/345/256/232_v1.0.md +1 -1
- package/docs/eval/issue52_/346/235/241/344/273/266/345/205/210/350/241/214/344/270/216/346/210/252/346/226/255/345/217/257/350/247/202/346/265/213_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +3 -3
- package/docs/eval//344/270/211/346/241/243/350/207/252/346/262/273_/346/255/245/351/252/244/342/221/242/345/217/230/346/233/264/345/215/225/344/270/216/345/233/236/346/273/232/345/216/237/350/257/255_/350/220/275/347/240/201/350/256/260/345/275/225_v1.0.md +3 -3
- package/docs/eval//344/270/211/346/241/243/350/207/252/346/262/273_/346/255/245/351/252/244/342/221/243/345/207/206/345/205/245/350/257/273/346/225/260_/350/220/275/347/240/201/350/256/260/345/275/225_v1.0.md +7 -7
- package/docs/eval//344/274/230/345/214/226/347/254/254/344/270/200/346/211/271_/346/216/245/347/272/277/344/270/216/347/255/211/344/273/267/345/217/230/346/215/242_v1.0.md +2 -2
- package/docs/eval//344/274/230/345/214/226/347/254/254/344/270/211/346/211/271_/351/227/250/347/246/201/350/275/254/346/255/243/344/270/216/350/260/203/345/272/246/346/255/242/350/241/200/344/270/216/351/227/250/346/216/247/346/224/266/345/217/243_v1.0.md +1 -1
- package/docs/eval//344/274/230/345/214/226/347/254/254/344/272/214/346/211/271_/344/276/235/350/265/226/351/200/217/344/274/240/344/270/216/345/257/271/346/213/215/345/217/243/345/276/204/344/270/216/350/264/237/347/274/223/345/255/230_v1.0.md +7 -7
- package/docs/eval//345/207/272/350/264/247/351/235/242/345/206/222/347/203/237_/350/277/233/350/264/247/351/227/250/347/246/201_v1.0.md +10 -10
- package/docs/eval//345/217/221/345/270/20307_/345/244/226/351/203/250/346/212/245/345/221/212/345/233/233/346/211/271/344/277/256/345/244/215/344/270/216/346/217/222/344/273/266/351/235/242/345/212/240/345/233/272_v1.0.md +4 -0
- package/docs/eval//345/217/221/345/270/20308_/350/207/252/350/277/255/344/273/243/344/270/216/347/235/241/347/234/240_/345/233/276/346/243/200/347/264/242/350/267/257/344/270/216/346/235/203/351/207/215_v1.0.md +1 -1
- package/docs/eval//345/217/221/345/270/20311_/345/244/226/346/212/245/345/205/255/350/277/236/344/277/256/344/270/216/344/270/226/347/225/214/346/250/241/345/236/213/345/272/225/345/272/247_v1.0.md +91 -0
- package/docs/eval//345/217/221/345/270/20312_/344/270/226/347/225/214/346/250/241/345/236/213/345/212/237/350/203/275/347/253/257_v1.0.md +108 -0
- package/docs/eval//345/275/222/344/270/200/345/261/202/347/274/272/347/234/201/347/277/273/345/205/263_/344/277/256/345/244/215/350/256/260/345/275/225_v1.0.md +2 -2
- package/docs/eval//347/224/250/346/210/267/350/243/201/345/206/263/345/244/204/347/275/256_/347/254/254/344/270/200/346/211/271_v1.0.md +195 -0
- package/docs/eval//347/224/250/346/210/267/350/243/201/345/206/263/345/244/204/347/275/256_/347/254/254/344/272/214/346/211/271B6_/346/234/254/346/234/272/350/267/257/345/276/204/347/233/270/345/257/271/345/214/226_v1.0.md +78 -0
- package/docs/eval//347/253/257/345/210/260/347/253/257LoCoMoQA/345/220/214/345/217/243/345/276/204/345/257/271/347/205/247_v1.0.md +1 -1
- package/docs/eval//347/253/257/345/210/260/347/253/257/345/271/262/346/211/260/346/261/240/350/257/204/346/265/213_/347/241/256/345/256/232/346/200/247/350/243/201/345/206/263vsLLM_judge_v1.1.md +3 -3
- package/docs/eval//347/254/2543/345/261/202stg/347/273/223/346/236/204/347/264/242/345/274/225_/345/256/236/346/226/275/350/256/260/345/275/225_v1.0.md +1 -1
- package/docs/eval//347/274/226/347/240/201/351/235/242/345/211/215/347/275/256_/345/205/245/345/217/243/350/207/252/344/277/235/350/257/201UTF8/344/270/216/346/226/207/346/234/254open/345/256/210/345/215/253_v1.0.md +1 -1
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v26.md +196 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v27.md +259 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v28.md +217 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v29.md +270 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v30.md +231 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v31.md +281 -0
- package/docs/eval//350/257/255/344/271/211/346/227/266/347/251/272/345/233/276/350/241/245/345/205/250_P1_/346/212/275/345/217/226/345/231/250v4_/344/270/211/347/216/207/350/257/273/346/225/260_v1.0.md +231 -0
- package/docs/hive//345/256/211/345/205/250/345/256/241/350/256/241/345/256/236/351/224/232_v0.1.md +1 -1
- package/docs/hive//350/234/202/345/267/242M6_ingest/345/256/236/346/226/275/350/256/241/345/210/222_v0.1.md +1 -1
- 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 +1 -1
- package/docs/hive//350/234/202/345/267/242/350/256/276/350/256/241_/347/220/206/350/256/272/345/257/271/351/275/220_v0.1.md +17 -17
- package/docs/hive//350/234/202/345/267/242/350/277/255/344/273/243_/345/256/217/350/247/202/344/270/216/347/276/244/344/275/223/350/260/203/345/272/246_v0.1.md +1 -1
- package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +2 -2
- package/docs/mdcg//344/270/226/347/225/214/346/250/241/345/236/213/345/212/237/350/203/275/347/253/257_/344/275/277/347/224/250/344/270/216/350/277/220/347/273/264_v1.0.md +305 -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 +45 -42
- package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +29 -8
- package/docs/mdcg//347/235/241/347/234/240/345/221/250/346/234/237_/350/277/220/347/273/264/345/211/215/346/217/220/344/270/216/347/273/264/346/212/244/346/214/207/345/215/227_v1.0.md +71 -1
- package/docs/plans//345/205/250/344/270/255/346/226/207/347/274/226/347/240/201/344/270/216/350/234/202/345/267/242/344/273/273/345/212/241/346/240/207/350/257/206/345/245/221/347/272/246_v2.0.md +1 -1
- package/docs/plans//345/244/232/344/270/273/344/275/223/344/270/226/347/225/214/346/250/241/345/236/213_/345/257/271/351/275/220/350/257/204/344/274/260/344/270/216/350/220/275/345/234/260/350/256/276/350/256/241_v0.1.md +141 -0
- package/docs/plans//347/235/241/347/234/240/344/270/216/350/207/252/350/277/255/344/273/243_/345/212/237/350/203/275/344/274/230/345/214/226/350/256/276/350/256/241_v0.4.md +2 -2
- package/docs/plans//350/257/255/344/271/211/346/227/266/347/251/272/345/233/276/350/241/245/345/205/250_/344/270/226/347/225/214/346/250/241/345/236/213/345/212/237/350/203/275/347/253/257_/350/256/276/350/256/241_v0.1.md +107 -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 +6 -5
- package/docs//347/247/244_/350/256/260/345/277/206/347/263/273/347/273/237/350/257/204/346/265/213/350/247/204/350/214/203_v2.1.md +225 -0
- package/dsh/cordis-patch-profile-web.example.yml +2 -2
- package/lib/bridge.js +9 -0
- package/lib/hooks.d.ts +8 -1
- package/lib/hooks.js +134 -50
- package/lib/lib/hook_audit.d.ts +55 -0
- package/lib/lib/hook_audit.js +208 -0
- package/lib/lib/mutual.js +5 -1
- package/lib/lib/roleplay_web.js +17 -2
- package/lib/lib/session_state.d.ts +56 -0
- package/lib/lib/session_state.js +124 -0
- package/lib/tools.js +12 -1
- package/md_cg/auditview.py +248 -0
- package/md_cg/bench_e2e_judge.py +4 -2
- package/md_cg/branches.py +29 -3
- package/md_cg/consistency.py +38 -13
- package/md_cg/datapath.py +38 -11
- package/md_cg/forgetting.py +15 -2
- package/md_cg/freshness.py +32 -11
- package/md_cg/ghostref.py +130 -0
- package/md_cg/judgment_manifest.py +7 -0
- package/md_cg/lifecycle.py +38 -0
- package/md_cg/mcp_server.py +122 -12
- package/md_cg/mdcg.py +4923 -4822
- package/md_cg/mdcos.py +109 -21
- package/md_cg/nodefile.py +77 -13
- package/md_cg/readcache.py +29 -4
- package/md_cg/selfreport.py +12 -3
- package/md_cg/sleep.py +129 -13
- package/md_cg/sources.py +1 -1
- package/md_cg/state_events.py +131 -0
- package/md_cg/state_slots.py +221 -0
- package/md_cg/stg.py +68 -3
- package/md_cg/sustain.py +1436 -1436
- package/md_cg/test_auditview.py +277 -0
- package/md_cg/test_autonomy_modes.py +2 -1
- package/md_cg/test_boot_openblas_guard.py +197 -0
- package/md_cg/test_ccg_form_parity.py +95 -4
- package/md_cg/test_cons200_scan_selection.py +4 -2
- package/md_cg/test_datapath_device_name.py +62 -3
- package/md_cg/test_generation_guard.py +5 -2
- package/md_cg/test_ghostref.py +182 -0
- package/md_cg/test_interop.py +1 -1
- package/md_cg/test_issue52_scan_condition_first.py +1 -1
- package/md_cg/test_lifecycle_retire_leak.py +408 -0
- package/md_cg/test_lock.py +16 -7
- package/md_cg/test_m3_h9_semantic_guard.py +1 -1
- package/md_cg/test_n230_dirty_replay.py +375 -375
- package/md_cg/test_n238_body_text_parity.py +156 -0
- package/md_cg/test_n276_probe_hoist.py +199 -0
- package/md_cg/test_p4_freshness.py +95 -1
- package/md_cg/test_policy_required_ccg.py +3 -1
- package/md_cg/test_read_face_semantics.py +6 -3
- package/md_cg/test_readcache_prodpath.py +66 -0
- package/md_cg/test_sleep.py +58 -1
- package/md_cg/test_sleep_p1.py +218 -2
- package/md_cg/test_state_event_op.py +514 -0
- package/md_cg/test_state_events.py +178 -0
- package/md_cg/test_state_slots.py +691 -0
- package/md_cg/test_time_core_lint.py +8 -1
- package/md_cg/test_writepipe.py +8 -5
- package/md_cg/tokens.py +18 -2
- package/md_cg/writepipe.py +76 -0
- package/package.json +2 -2
- package/skills/plugin.json +1 -1
- package/src/bridge.ts +9 -0
- package/src/hooks.ts +599 -517
- package/src/lib/hook_audit.ts +243 -0
- package/src/lib/mutual.ts +5 -1
- package/src/lib/roleplay_web.ts +17 -2
- package/src/lib/session_state.ts +127 -0
- package/src/tools.ts +12 -1
- package/zcode/AGENTS.md +6 -6
package/md_cg/sleep.py
CHANGED
|
@@ -31,12 +31,18 @@
|
|
|
31
31
|
|
|
32
32
|
四阶段(§4.4)里本模块的落点:
|
|
33
33
|
|
|
34
|
-
① 物化 `materialize()` ——
|
|
34
|
+
① 物化 `materialize()` —— 持根级锁;挂上影子即成功(`ok=True`);
|
|
35
|
+
真源面在窗口内漂移只记 `face_stable=False`,
|
|
36
|
+
**不阻断迭代**(漂移交 ③ 的并发闸按「主库
|
|
37
|
+
优先」逐项处置——issue #60)
|
|
35
38
|
② 迭代 (P1;在影子上跑,本模块不管)
|
|
36
39
|
③ 对账+提交 `reconcile_and_commit()` —— 持根级锁;Δ 为空即零提交零写入
|
|
37
40
|
④ 合并 `merge()` —— 持根级锁;写主库的正是「应写的那部分」
|
|
38
41
|
|
|
39
|
-
|
|
42
|
+
脚本式用法:`python -X utf8 -m md_cg.sleep --status`(只读判读,含上一轮读数)|
|
|
43
|
+
`--once`(立即跑一轮,不等常驻 tick,同样过四道门)|
|
|
44
|
+
`--once --dry-run`(只预览:跑盘点与四道门,不物化、不记账)|
|
|
45
|
+
`--audit`(只跑两条机械判据)
|
|
40
46
|
"""
|
|
41
47
|
from __future__ import annotations
|
|
42
48
|
|
|
@@ -352,7 +358,7 @@ def _busy(phase: str, timeout: float) -> dict:
|
|
|
352
358
|
"只有一个能进)")}
|
|
353
359
|
|
|
354
360
|
|
|
355
|
-
# 生效条件:root 为数据根(缺省取 mdcg_root());在 <state_root>/sleep/sleep 的根级锁内,建/复用独立 git 目录并(首次)把真源面入册为 main 基线,再 git worktree add -b sleep/<ts> <shadow> main(分支已存在则复用该分支重试一次);返回含 ok/phase/busy/git_dir/shadow/branch/pathspecs/
|
|
361
|
+
# 生效条件:root 为数据根(缺省取 mdcg_root());在 <state_root>/sleep/sleep 的根级锁内,建/复用独立 git 目录并(首次)把真源面入册为 main 基线,再 git worktree add -b sleep/<ts> <shadow> main(分支已存在则复用该分支重试一次);返回含 ok/face_stable/phase/busy/lock_held/git_dir/shadow/branch/pathspecs/baseline/source_face_delta/lock 的字典——**ok = 物化动作是否成功**(正常挂上影子的成功路径恒真;取不到根级锁或影子未能挂上时保持假);**face_stable = 主库真源面在物化窗口内逐字节未变**(原 ok 的真值;漂移不阻断物化,交 ③ 的并发闸按「主库优先」处置)。
|
|
356
362
|
def materialize(root: str = None, *, git_dir_path: str = None,
|
|
357
363
|
shadow: str = None, ts: str = None,
|
|
358
364
|
timeout: float = DEFAULT_LOCK_TIMEOUT) -> dict:
|
|
@@ -360,6 +366,11 @@ def materialize(root: str = None, *, git_dir_path: str = None,
|
|
|
360
366
|
|
|
361
367
|
幂等:影子已存在时先摘(`worktree remove --force`,非注册目录退化为 rmtree)
|
|
362
368
|
再挂。分支已存在(同一秒重入)时退化为复用该分支。
|
|
369
|
+
|
|
370
|
+
`ok` 与 `face_stable` 是**两件事**(issue #60):前者只说「影子挂上了没」,
|
|
371
|
+
后者才是「物化窗口内主库真源面有没有被别的写方动过」。窗口内漂移**不阻断**
|
|
372
|
+
迭代——活跃工作区(每轮都有新增 contextual/*.md)正是如此;同一 id 的改动
|
|
373
|
+
由 ③ 的并发闸(`baseline_face` 水位,主库优先)逐项裁决,那才是漂移的处置点。
|
|
363
374
|
"""
|
|
364
375
|
root = os.path.abspath(root or mdcg_root())
|
|
365
376
|
G = git_dir_path or git_dir()
|
|
@@ -391,7 +402,11 @@ def materialize(root: str = None, *, git_dir_path: str = None,
|
|
|
391
402
|
reused = False
|
|
392
403
|
after = source_face_hashes(root)
|
|
393
404
|
delta = face_delta(before, after)
|
|
394
|
-
|
|
405
|
+
# ok = **物化动作成功**(影子已挂上);真源面漂移与否是另一个读数
|
|
406
|
+
# (face_stable),不再拿来否决整轮迭代(issue #60)。
|
|
407
|
+
return {"ok": True,
|
|
408
|
+
"face_stable": not (delta["added"] or delta["removed"]
|
|
409
|
+
or delta["changed"]),
|
|
395
410
|
"busy": False, "lock_held": True, "phase": "materialize",
|
|
396
411
|
"git_dir": G, "shadow": S, "branch": branch,
|
|
397
412
|
"branch_reused": reused, "repo_created": rep["created"],
|
|
@@ -655,6 +670,34 @@ def batch_merged(batch: str) -> bool:
|
|
|
655
670
|
for r in read_ledger())
|
|
656
671
|
|
|
657
672
|
|
|
673
|
+
# 生效条件:无入参;返回台账里的**下一轮序号**(已有记录中最大的 round + 1;空台账/无有效 round 时为 1)——CLI `--once` 接着历史轮次编号,不另起一套计数。
|
|
674
|
+
def next_round_index() -> int:
|
|
675
|
+
nums = [r.get("round") for r in read_ledger()]
|
|
676
|
+
nums = [n for n in nums if isinstance(n, int) and not isinstance(n, bool)]
|
|
677
|
+
return (max(nums) + 1) if nums else 1
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
# 生效条件:rec 为 run_cycle 落台账的记录字典时,返回 ⑦记录步里的 phases 字典(缺该步或形态不符时返回 {})。
|
|
681
|
+
def _record_phases(rec: dict) -> dict:
|
|
682
|
+
for s in (rec.get("steps") or []):
|
|
683
|
+
if isinstance(s, dict) and s.get("step") == "record":
|
|
684
|
+
return s.get("phases") or {}
|
|
685
|
+
return {}
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
# 生效条件:rec 为 run_cycle 落台账的记录字典时返回「本轮未迭代原因」文案或 None——先取门拦 `rec["skipped"]`,再取 ②③④ 的 skip 文案(物化未完成等);已迭代的一轮返回 None。CLI `--once` 与 `--status` 的同一文案源(不另写第二份判据)。
|
|
689
|
+
def cycle_not_iterated_reason(rec: dict):
|
|
690
|
+
if not isinstance(rec, dict):
|
|
691
|
+
return None
|
|
692
|
+
if rec.get("skipped"):
|
|
693
|
+
return rec["skipped"]
|
|
694
|
+
for s in (rec.get("steps") or []):
|
|
695
|
+
if isinstance(s, dict) and s.get("step") in ("induce", "promote", "scrub") \
|
|
696
|
+
and s.get("skipped"):
|
|
697
|
+
return s["skipped"]
|
|
698
|
+
return None
|
|
699
|
+
|
|
700
|
+
|
|
658
701
|
# 生效条件:root 给定且 md_cg.mdcos 可导入时返回该 root 上的 MdCGOS 实例;导入失败抛原异常。
|
|
659
702
|
def _open_cg(root: str):
|
|
660
703
|
from .mdcos import MdCGOS
|
|
@@ -1151,13 +1194,19 @@ def ledger_steps(*, scan: dict, iterated: dict = None, delta_digest: dict = None
|
|
|
1151
1194
|
return steps
|
|
1152
1195
|
|
|
1153
1196
|
|
|
1154
|
-
# 生效条件:给定 cg(主库)与各相位开关时,按 §3.2 的稳态判据与 §4.4 的四阶段跑一轮睡眠周期并返回台账记录;enabled=False / merge_mode="never" / 窗口外 / 候选数为 0 四种情形**只记账不动手**(②③④记
|
|
1197
|
+
# 生效条件:给定 cg(主库)与各相位开关时,按 §3.2 的稳态判据与 §4.4 的四阶段跑一轮睡眠周期并返回台账记录;enabled=False / merge_mode="never" / 窗口外 / 候选数为 0 四种情形**只记账不动手**(②③④记 gate_reason);物化未完成(busy/error)时 ②③④ 记「物化未完成:…」(与门拦文案**区分**);batch 为已合并批次时直接返回 {"idempotent": True} 且零动作;dry_run=True 时只跑感知盘点与四道门评估(**不物化、不记账**)。
|
|
1155
1198
|
def run_cycle(cg=None, *, root: str = None, batch: str = None,
|
|
1156
1199
|
enabled: bool = True, merge_mode: str = None,
|
|
1157
1200
|
scrub_apply: bool = None, window: str = None,
|
|
1158
1201
|
round_index: int = 1, now=None, git_dir_path: str = None,
|
|
1159
|
-
shadow: str = None,
|
|
1160
|
-
|
|
1202
|
+
shadow: str = None, dry_run: bool = False,
|
|
1203
|
+
timeout: float = DEFAULT_LOCK_TIMEOUT) -> dict:
|
|
1204
|
+
"""一轮睡眠周期(§3.1 九步 + §4.4 四阶段)。
|
|
1205
|
+
|
|
1206
|
+
`dry_run=True` 是**预览档**(`--once --dry-run`):只跑 ① 感知盘点与四道门
|
|
1207
|
+
评估(总开关 / 合并策略 / 窗口 / 稳态),**不物化、不记账**——早退点刻意
|
|
1208
|
+
置于 `materialize` 之前,也不触 `_append_ledger`。
|
|
1209
|
+
"""
|
|
1161
1210
|
root = os.path.abspath(root or (cg.root if cg is not None else mdcg_root()))
|
|
1162
1211
|
if cg is None:
|
|
1163
1212
|
cg = _open_cg(root)
|
|
@@ -1179,7 +1228,10 @@ def run_cycle(cg=None, *, root: str = None, batch: str = None,
|
|
|
1179
1228
|
candidates=None, steps=ledger_steps(
|
|
1180
1229
|
scan={}, skip_reason="幂等:同批次已合并",
|
|
1181
1230
|
round_index=round_index, prev_candidates=prev))
|
|
1182
|
-
|
|
1231
|
+
if dry_run:
|
|
1232
|
+
rec["dry_run"] = True # 预览档:**不记账**
|
|
1233
|
+
else:
|
|
1234
|
+
_append_ledger(rec)
|
|
1183
1235
|
return rec
|
|
1184
1236
|
|
|
1185
1237
|
# ① 感知盘点(主库,只读)——稳态判据的口径
|
|
@@ -1200,14 +1252,37 @@ def run_cycle(cg=None, *, root: str = None, batch: str = None,
|
|
|
1200
1252
|
elif not scan["candidates"]:
|
|
1201
1253
|
gate_reason = "稳态:盘点候选数为 0(只记账不动手)"
|
|
1202
1254
|
|
|
1255
|
+
if dry_run:
|
|
1256
|
+
# 预览档:四道门已评完即止——**不物化、不记账**(连影子都不建)。
|
|
1257
|
+
rec.update(dry_run=True, skipped=gate_reason,
|
|
1258
|
+
planned=(None if gate_reason
|
|
1259
|
+
else "将执行物化→迭代→对账→合并"),
|
|
1260
|
+
steps=ledger_steps(scan=scan, round_index=round_index,
|
|
1261
|
+
prev_candidates=prev,
|
|
1262
|
+
skip_reason=gate_reason or ""))
|
|
1263
|
+
return rec
|
|
1264
|
+
|
|
1203
1265
|
phases, iterated, delta, digest = {}, None, {}, {"n": 0, "ids": [], "kinds": {}}
|
|
1266
|
+
mat_fail = None
|
|
1204
1267
|
if gate_reason is None:
|
|
1205
1268
|
m = materialize(root, git_dir_path=git_dir_path, shadow=shadow,
|
|
1206
1269
|
ts=b, timeout=timeout)
|
|
1207
1270
|
phases["materialize"] = {k: m.get(k) for k in
|
|
1208
|
-
("ok", "
|
|
1209
|
-
"baseline", "source_face_delta"
|
|
1271
|
+
("ok", "face_stable", "busy", "branch", "shadow",
|
|
1272
|
+
"git_dir", "baseline", "source_face_delta",
|
|
1273
|
+
"reason", "error")}
|
|
1274
|
+
if not m.get("ok"):
|
|
1275
|
+
# 物化**未完成**(取不到根级锁 / 影子未能挂上)——与「门拦」区分记账:
|
|
1276
|
+
# 门拦是「按设计不动手」,这里是「想动手却没做成」,用户要能一眼分开。
|
|
1277
|
+
mat_fail = ("物化未完成:根级锁被占(另一进程在周期内)"
|
|
1278
|
+
if m.get("busy")
|
|
1279
|
+
else "物化未完成:%s" % (m.get("error") or "未知原因"))
|
|
1210
1280
|
if m.get("ok"):
|
|
1281
|
+
# **物化成功即迭代**(issue #60):`ok` 现在只说「影子挂上了没」;
|
|
1282
|
+
# 物化窗口内主库真源面的漂移另记 `face_stable`,**不阻断**迭代——
|
|
1283
|
+
# 活跃工作区(每轮都有新写入)本就如此。迭代期间主库被改动/新增的
|
|
1284
|
+
# 同一 id,由下面对账段的并发闸(`baseline_face` 水位)逐项裁
|
|
1285
|
+
# 「主库优先」——那才是漂移的处置点,不是在这里自我否决。
|
|
1211
1286
|
S = m["shadow"]
|
|
1212
1287
|
iterated = iterate(S, scrub_apply=scrub)
|
|
1213
1288
|
phases["iterate"] = iterated
|
|
@@ -1255,25 +1330,64 @@ def run_cycle(cg=None, *, root: str = None, batch: str = None,
|
|
|
1255
1330
|
rec["steps"] = ledger_steps(scan=scan, iterated=iterated,
|
|
1256
1331
|
delta_digest=digest, phases=phases,
|
|
1257
1332
|
round_index=round_index, prev_candidates=prev,
|
|
1258
|
-
skip_reason=gate_reason or "")
|
|
1333
|
+
skip_reason=gate_reason or mat_fail or "")
|
|
1259
1334
|
rec["skipped"] = gate_reason
|
|
1260
1335
|
_append_ledger(rec)
|
|
1261
1336
|
return rec
|
|
1262
1337
|
|
|
1263
1338
|
|
|
1264
|
-
# 生效条件:
|
|
1339
|
+
# 生效条件:recs 为 read_ledger(limit=1) 的返回(空列表或单条记录)时,返回该记录的 CLI 视图(t/batch/round/candidates/skipped / ②③④ skip 文案 / phases.materialize 摘要 / 未迭代原因);无记录返回 None。
|
|
1340
|
+
def _last_cycle_view(recs):
|
|
1341
|
+
if not recs:
|
|
1342
|
+
return None
|
|
1343
|
+
rec = recs[-1]
|
|
1344
|
+
return {"t": rec.get("t"), "batch": rec.get("batch"),
|
|
1345
|
+
"round": rec.get("round"), "candidates": rec.get("candidates"),
|
|
1346
|
+
"skipped": rec.get("skipped"),
|
|
1347
|
+
"steps_skip": {s.get("step"): s.get("skipped")
|
|
1348
|
+
for s in (rec.get("steps") or [])
|
|
1349
|
+
if isinstance(s, dict)
|
|
1350
|
+
and s.get("step") in ("induce", "promote", "scrub")},
|
|
1351
|
+
"materialize": _record_phases(rec).get("materialize"),
|
|
1352
|
+
"not_iterated_reason": cycle_not_iterated_reason(rec)}
|
|
1353
|
+
|
|
1354
|
+
|
|
1355
|
+
# 生效条件:argv 含 --once 时以 env 读取器为参数跑一轮 run_cycle(round 取 next_round_index();--dry-run 同用时只预览、不物化不记账)并打印整条记录与「本轮未迭代原因」(未迭代时,文案与台账同源);argv 含 --audit 时只打印两条机械判据结论(不合规返回 1);--dry-run **不与 --once 同用**时返回 2(报错,不执行任何动作);其余情形打印当前解析结果(git_dir / shadow / lock / pathspecs / tracking / history / **last_cycle** 读数)的 JSON 并返回 0。
|
|
1265
1356
|
def _main(argv=None) -> int:
|
|
1266
|
-
ap = argparse.ArgumentParser(description="睡眠周期 git
|
|
1357
|
+
ap = argparse.ArgumentParser(description="睡眠周期 git 机制(只读判读 + 手动一轮)")
|
|
1267
1358
|
ap.add_argument("--root", default=None, help="数据根(缺省 mdcg_root())")
|
|
1268
1359
|
ap.add_argument("--git-dir", dest="git_dir_path", default=None)
|
|
1269
1360
|
ap.add_argument("--shadow", default=None)
|
|
1270
1361
|
ap.add_argument("--status", action="store_true", help="打印当前解析与判据读数")
|
|
1271
1362
|
ap.add_argument("--audit", action="store_true", help="只跑两条机械判据")
|
|
1363
|
+
ap.add_argument("--once", action="store_true",
|
|
1364
|
+
help="立即跑一轮睡眠周期(不等常驻循环的 tick;同样过四道门)")
|
|
1365
|
+
ap.add_argument("--dry-run", dest="dry_run", action="store_true",
|
|
1366
|
+
help="只预览(须与 --once 同用):跑盘点与四道门,不物化、不记账")
|
|
1272
1367
|
a = ap.parse_args(argv)
|
|
1368
|
+
if a.dry_run and not a.once:
|
|
1369
|
+
sys.stderr.write("--dry-run 须与 --once 同用:"
|
|
1370
|
+
"单独使用不执行任何动作(预览一轮请用 --once --dry-run)\n")
|
|
1371
|
+
return 2
|
|
1273
1372
|
root = os.path.abspath(a.root or mdcg_root())
|
|
1274
1373
|
G = a.git_dir_path or git_dir()
|
|
1275
1374
|
shadow = a.shadow or shadow_dir()
|
|
1276
1375
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
1376
|
+
if a.once:
|
|
1377
|
+
rec = run_cycle(None, root=root, enabled=sleep_enabled(),
|
|
1378
|
+
merge_mode=sleep_merge_mode(),
|
|
1379
|
+
scrub_apply=sleep_scrub_apply(), window=sleep_window(),
|
|
1380
|
+
round_index=next_round_index(), dry_run=a.dry_run,
|
|
1381
|
+
git_dir_path=a.git_dir_path, shadow=a.shadow)
|
|
1382
|
+
print(json.dumps(rec, ensure_ascii=False, indent=2))
|
|
1383
|
+
if rec.get("dry_run"):
|
|
1384
|
+
print("预览:" + (rec.get("planned")
|
|
1385
|
+
or ("门拦:%s" % (rec.get("skipped") or ""))))
|
|
1386
|
+
else:
|
|
1387
|
+
why = cycle_not_iterated_reason(rec)
|
|
1388
|
+
if why:
|
|
1389
|
+
print("本轮未迭代原因:" + why)
|
|
1390
|
+
return 0
|
|
1277
1391
|
if a.audit:
|
|
1278
1392
|
tr = audit_tracking(root, git_dir_path=G)
|
|
1279
1393
|
hi = audit_history(root, git_dir_path=G)
|
|
@@ -1289,6 +1403,8 @@ def _main(argv=None) -> int:
|
|
|
1289
1403
|
if os.path.isdir(G):
|
|
1290
1404
|
out["tracking"] = audit_tracking(root, git_dir_path=G)
|
|
1291
1405
|
out["history"] = audit_history(root, git_dir_path=G)
|
|
1406
|
+
# 上一轮读数:从 CLI 就能看到「上次为何没迭代」(门拦 / 物化未完成 / 幂等)。
|
|
1407
|
+
out["last_cycle"] = _last_cycle_view(read_ledger(limit=1))
|
|
1292
1408
|
print(json.dumps(out, ensure_ascii=False, indent=2))
|
|
1293
1409
|
return 0
|
|
1294
1410
|
|
package/md_cg/sources.py
CHANGED
|
@@ -258,7 +258,7 @@ class DSHSessionSource(Source):
|
|
|
258
258
|
|
|
259
259
|
|
|
260
260
|
# --------------------------------------------------------------------------
|
|
261
|
-
# 蜂巢任务事件源(M6:hive/jobs → contextual
|
|
261
|
+
# 蜂巢任务事件源(M6:hive/jobs → contextual,外部设计稿 蜂巢记忆架构设计.md §5)
|
|
262
262
|
# --------------------------------------------------------------------------
|
|
263
263
|
|
|
264
264
|
class HiveJobsSource(Source):
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""状态事件账本(append-only)——多主体世界模型 L0「事件必记账」的最小承载。
|
|
3
|
+
|
|
4
|
+
背景(对齐评估 v0.1,§2 L0):CD-WHALE-01(鲸娘语料 731 轮)的六类缺陷全部落在
|
|
5
|
+
「迁移/撤回/启用/来源」四个**状态操作**上(属性记得住、状态记不住);CTP-P5
|
|
6
|
+
回放的敏感性边界一证明「现行侧若只有状态陈述(T2)而无事件记录(T1),设施回滚
|
|
7
|
+
只能判 DEFER」——故「事件必记账」是状态链(基线→变更→后续引用)与退役/否定
|
|
8
|
+
语义的前置(跨切纪律 1)。
|
|
9
|
+
|
|
10
|
+
设计约束(对齐评估 v0.1 复核修订后的口径):
|
|
11
|
+
· **append-only**:`_recent.jsonl` 为窗口滚动面(`DEFAULT_RECENT_WINDOW=200`,
|
|
12
|
+
`roll_recent` 超限即截断),不可作一等记录;本账本只追加、不读改写、不截断。
|
|
13
|
+
· **单一真源**:事件是源、槽位现值是投影(未来 L2 寄存器由本账本物化;
|
|
14
|
+
落盘位置与 `_evolution` 台账/审计同级:`<root>/_state_events.jsonl`)。
|
|
15
|
+
· **五元结构**:主体 subject / 槽位 slot / 旧值 old → 新值 new / 轮或序号
|
|
16
|
+
seq / 出处 evidence(+ kind 五类变迁事件 + actor 断言者 + t 时间戳)。
|
|
17
|
+
|
|
18
|
+
KINDS 五类对齐《角色扮演一致性锚调研 v0.1》§3.4「变迁事件」:
|
|
19
|
+
迁移 migration / 撤回 retraction / 启用 enablement / 购买获得 acquisition /
|
|
20
|
+
替换 replacement。「撤回」语义:old 有值而 new 为空(值作废、无新值);
|
|
21
|
+
「启用」:old 空而 new 有值;「迁移/替换」:old 与 new 皆有值。
|
|
22
|
+
分类本身不进判据(记账不判语义),只作检索面标签。
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import json
|
|
27
|
+
import os
|
|
28
|
+
import sys
|
|
29
|
+
import time
|
|
30
|
+
|
|
31
|
+
from .fsutil import FileLock
|
|
32
|
+
|
|
33
|
+
#: 账本文件名(库根下,与 `_recent.jsonl`/`_write_2pc.jsonl` 同级)。
|
|
34
|
+
STATE_EVENTS_NAME = "_state_events.jsonl"
|
|
35
|
+
|
|
36
|
+
#: 五类变迁事件(角色扮演一致性锚调研 v0.1 §3.4)。
|
|
37
|
+
KINDS = ("migration", "retraction", "enablement", "acquisition", "replacement")
|
|
38
|
+
|
|
39
|
+
#: 坏行容忍记账(容忍 ≠ 静默——本仓 N225 已确立的判据:跳过要可观测)。
|
|
40
|
+
BAD_ROWS = 0 # 累计**跳过动作次数**(同一坏行每次 read 均计一次;非按行去重)
|
|
41
|
+
_BAD_ROW_SAMPLES = [] # 有界样本:(行号, 行首 80 字符, 异常类型名)
|
|
42
|
+
_BAD_ROW_SAMPLE_CAP = 8
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _note_bad_row(lineno: int, line: str, exc: Exception):
|
|
46
|
+
global BAD_ROWS
|
|
47
|
+
BAD_ROWS += 1
|
|
48
|
+
if len(_BAD_ROW_SAMPLES) < _BAD_ROW_SAMPLE_CAP:
|
|
49
|
+
_BAD_ROW_SAMPLES.append((lineno, line[:80], type(exc).__name__))
|
|
50
|
+
sys.stderr.write(
|
|
51
|
+
f"[state_events] 跳过坏行(第 {lineno} 行,累计 {BAD_ROWS}):"
|
|
52
|
+
f"{type(exc).__name__}: {exc}\n")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# 生效条件:cg 可解析出 root 时,返回 os.path.join(cg.root, STATE_EVENTS_NAME)。
|
|
56
|
+
def ledger_path(cg) -> str:
|
|
57
|
+
return os.path.join(cg.root, STATE_EVENTS_NAME)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# 生效条件:subject 与 slot 去空白后必须非空(否则 ValueError);kind 非 None 时必须属于
|
|
61
|
+
# KINDS(否则 ValueError);old 与 new 不得同时为 None(否则 ValueError);通过后在
|
|
62
|
+
# FileLock(ledger_path) 内以追加模式写一行 JSON(ensure_ascii=False,flush+fsync),
|
|
63
|
+
# 返回落盘记录 dict(不改任何既有文件、不读改写、不截断)。
|
|
64
|
+
def append(cg, subject, slot, old=None, new=None, kind=None, seq=None,
|
|
65
|
+
evidence="", actor="", extra=None) -> dict:
|
|
66
|
+
"""追加一条状态事件(append-only;跨进程锁;崩溃不留半截文件)。"""
|
|
67
|
+
sub = str(subject or "").strip()
|
|
68
|
+
sl = str(slot or "").strip()
|
|
69
|
+
if not sub:
|
|
70
|
+
raise ValueError("状态事件必须声明主体(subject)")
|
|
71
|
+
if not sl:
|
|
72
|
+
raise ValueError("状态事件必须声明槽位(slot)")
|
|
73
|
+
if kind is not None and kind not in KINDS:
|
|
74
|
+
raise ValueError(f"未知变迁事件类型:{kind}(允许:{KINDS})")
|
|
75
|
+
if old is None and new is None:
|
|
76
|
+
raise ValueError("状态事件 old 与 new 不能同时为空(空事件无意义)")
|
|
77
|
+
rec = {
|
|
78
|
+
"t": time.time(),
|
|
79
|
+
"subject": sub,
|
|
80
|
+
"slot": sl,
|
|
81
|
+
"old": old,
|
|
82
|
+
"new": new,
|
|
83
|
+
"kind": kind,
|
|
84
|
+
"seq": seq,
|
|
85
|
+
"evidence": str(evidence or ""),
|
|
86
|
+
"actor": str(actor or ""),
|
|
87
|
+
}
|
|
88
|
+
if extra:
|
|
89
|
+
rec.update(extra)
|
|
90
|
+
p = ledger_path(cg)
|
|
91
|
+
os.makedirs(os.path.dirname(p) or ".", exist_ok=True)
|
|
92
|
+
with FileLock(p):
|
|
93
|
+
with open(p, "a", encoding="utf-8") as f:
|
|
94
|
+
f.write(json.dumps(rec, ensure_ascii=False) + "\n")
|
|
95
|
+
f.flush()
|
|
96
|
+
os.fsync(f.fileno())
|
|
97
|
+
return rec
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# 生效条件:ledger_path 不存在时返回 [];存在时逐行 json.loads(空行跳过;非对象载荷或
|
|
101
|
+
# JSON 解析失败按坏行处置——计数+有界样本+stderr 告警后跳过,不中断读);subject/slot/kind
|
|
102
|
+
# **非 None** 时按对应字段过滤(空串亦为合法过滤值,2026-10-05 复核口径钉正);limit 为
|
|
103
|
+
# 真值时只保留最后 limit 条;返回记录列表(落盘序)。
|
|
104
|
+
def read(cg, subject=None, slot=None, kind=None, limit=0) -> list:
|
|
105
|
+
"""读回(可按 subject/slot/kind 过滤;limit=0 不截断)。坏行跳过并记账。"""
|
|
106
|
+
p = ledger_path(cg)
|
|
107
|
+
out = []
|
|
108
|
+
if not os.path.exists(p):
|
|
109
|
+
return out
|
|
110
|
+
with open(p, encoding="utf-8", errors="replace") as f:
|
|
111
|
+
for i, line in enumerate(f, 1):
|
|
112
|
+
line = line.strip()
|
|
113
|
+
if not line:
|
|
114
|
+
continue
|
|
115
|
+
try:
|
|
116
|
+
rec = json.loads(line)
|
|
117
|
+
if not isinstance(rec, dict):
|
|
118
|
+
raise ValueError("行载荷非对象")
|
|
119
|
+
except Exception as e: # noqa: BLE001 —— 容忍≠静默:记账后跳过
|
|
120
|
+
_note_bad_row(i, line, e)
|
|
121
|
+
continue
|
|
122
|
+
if subject is not None and rec.get("subject") != subject:
|
|
123
|
+
continue
|
|
124
|
+
if slot is not None and rec.get("slot") != slot:
|
|
125
|
+
continue
|
|
126
|
+
if kind is not None and rec.get("kind") != kind:
|
|
127
|
+
continue
|
|
128
|
+
out.append(rec)
|
|
129
|
+
if limit:
|
|
130
|
+
out = out[-int(limit):]
|
|
131
|
+
return out
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""状态槽位寄存器投影(L2「主体×谓词槽位」)——**事件是源、槽位是投影**。
|
|
3
|
+
|
|
4
|
+
设计稿:`docs/plans/语义时空图补全_世界模型功能端_设计_v0.1.md` §2/§3(P2 批)。
|
|
5
|
+
|
|
6
|
+
本模块是**投影,不是第二真源**:
|
|
7
|
+
· 源=`md_cg/state_events.py` 的 append-only 台账 `<root>/_state_events.jsonl`;
|
|
8
|
+
· 查询时**现算**——不落盘、不建缓存、不写任何盘面文件;
|
|
9
|
+
· 台账追加一条,投影立刻随之变化(无缓存、无物化表 ⇒ 无第二真源、无漂移面);
|
|
10
|
+
· 本模块**只读**台账(经 `state_events.read`;`append` 归 `state_events`)。
|
|
11
|
+
|
|
12
|
+
投影单元字段(设计稿 §3 表逐字):
|
|
13
|
+
subject / slot / value / state(active|retired) / t_from / t_to /
|
|
14
|
+
seq_from / seq_to / tier / alternatives / blindspots / history / event_count
|
|
15
|
+
|
|
16
|
+
回放规则(分组键 `(subject, slot)`,组内按 `state_events.read()` 的**落盘序**):
|
|
17
|
+
· `value` 自 None 起:事件 `new` 非空 → `value=new`;`new` 空(撤回)→ `value=None`
|
|
18
|
+
且 `state=retired`;其后带值事件自然复活(事件序自然处理,无特判分支)。
|
|
19
|
+
· `state`:**最后一个事件的形态**决定——`new` 空 → `'retired'`;否则 `'active'`。
|
|
20
|
+
· 区间(取值一律用事件行的 `t` 落账时间戳,不另算时间):
|
|
21
|
+
`t_from`/`seq_from` =**最后产生当前值**的那条事件的 `t`/`seq`(retired 态=
|
|
22
|
+
最后一个**有值**事件的);`t_to`/`seq_to` =**覆盖它的下一条事件**的 `t`/`seq`
|
|
23
|
+
(不存在=None,即「未闭合/仍有效」);retired 态的覆盖者=撤回事件(闭合点
|
|
24
|
+
=值作废时刻)。
|
|
25
|
+
· `tier` =最后有值事件的顶层 `tier` 透传(`append(extra=…)` 会把 extra 并入行
|
|
26
|
+
顶层,故「extra.tier」与「行顶层 tier」是同一处);缺失(键不在或为 None)→
|
|
27
|
+
`TIER_UNSPECIFIED`——**不编造**。
|
|
28
|
+
· `alternatives` / `blindspots`:本批**结构在场、恒为空列表**——「同期未决值」与
|
|
29
|
+
「声明缺口」的判定口径**后置**(P1 事件行尚无该信号),**不得臆造填充**。
|
|
30
|
+
· `history` =该槽位全部事件原样(每条=事件行的 `t/seq/old/new/kind/evidence/
|
|
31
|
+
actor` 七字段,值不改写;缺键取 None);`history=False` 时**不带该键**。
|
|
32
|
+
|
|
33
|
+
撤回判据取 **payload**(`new` 为空)而不是 `kind` 标签:台账口径是「撤回=old 有值而
|
|
34
|
+
new 为空」(`state_events` 模块头),且该模块明言「分类本身不进判据(记账不判语义)」
|
|
35
|
+
——两者一致时等价;冲突时以 payload 为准(不按标签改写数据)。判据单点=`_is_void`。
|
|
36
|
+
|
|
37
|
+
过滤与排序:`subject`/`slot` 为 None 不加过滤(该过滤**委托** `state_events.read`,
|
|
38
|
+
此处不写第二套条件解析);`include_retired=False` 剔除 `state=='retired'` 的单元;
|
|
39
|
+
结果按 `(subject, slot)` 字典序稳定排序。
|
|
40
|
+
|
|
41
|
+
CLI(纯只读人工入口,**恒可用、无开关**):
|
|
42
|
+
python -X utf8 -m md_cg.state_slots --status
|
|
43
|
+
python -X utf8 -m md_cg.state_slots --subject 鲸娘 [--slot 住所]
|
|
44
|
+
数据根取 `MDCG_ROOT`(缺省走 `datapath.mdcg_root()`,与 `md_cg/sleep.py` 的
|
|
45
|
+
`_main` 同形)。本面与 stg 的 `MDCG_STG_STATE` 开关**无关**:开关管的是 MCP 读面,
|
|
46
|
+
CLI 是人工排查入口(只读、不改任何状态)。
|
|
47
|
+
|
|
48
|
+
不适用条件:本模块**不判语义**——「不是事实」的言语(玩笑/元层/回忆转述)之消歧
|
|
49
|
+
标记(`not_fact` 等)不在本批(P1 事件行无该信号);`alternatives`/`blindspots` 的
|
|
50
|
+
真值判定亦未落地(见上,恒空列表)。跨库/多租户合并不在此(单库=单台账)。
|
|
51
|
+
"""
|
|
52
|
+
from __future__ import annotations
|
|
53
|
+
|
|
54
|
+
import argparse
|
|
55
|
+
import json
|
|
56
|
+
import os
|
|
57
|
+
import sys
|
|
58
|
+
|
|
59
|
+
from . import state_events
|
|
60
|
+
|
|
61
|
+
#: 缺档位占位(台账未带 `tier` 时如实标注,不编造档位)。
|
|
62
|
+
TIER_UNSPECIFIED = "unspecified"
|
|
63
|
+
|
|
64
|
+
#: `history` 条目**必在**字段(单元 schema 逐字;缺键取 None,不改写数据)。
|
|
65
|
+
_HIST_FIELDS = ("t", "seq", "old", "new", "kind", "evidence", "actor")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# 生效条件:rec 为台账记录 dict——`rec.get("new")` 为 None 时返回 True(撤回/值作废),非 None 时返回 False;入参非 dict 时按 `.get` 抛 AttributeError(台账读面保证为 dict,不吞错)。
|
|
69
|
+
def _is_void(rec) -> bool:
|
|
70
|
+
"""撤回判据(单点):`new` 为空=值作废、无新值(口径见模块头)。"""
|
|
71
|
+
return rec.get("new") is None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# 生效条件:rec 为一条台账记录 dict 时,返回含 _HIST_FIELDS 七键(缺键取 None)的新 dict——字段集=单元 schema 的 history 条目逐字(不并入行内其余顶层键:新增键即「发明字段」,故限定枚举);值原样不改写、不改写入参。
|
|
75
|
+
def _hist_item(rec) -> dict:
|
|
76
|
+
"""变迁史单条:单元 schema 的七字段原样(值不改写)。"""
|
|
77
|
+
return {k: rec.get(k) for k in _HIST_FIELDS}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# 生效条件:subject/slot 为分组键、recs 为同组记录(**非空**,落盘序)、history 为真值时——按模块头回放规则返回单元 dict(含 history 键);history 为假值时同一单元但不带 history 键;recs 为空时抛 IndexError(组由记录现场建成,恒非空,不吞错)。
|
|
81
|
+
def _replay(subject, slot, recs, *, history=True) -> dict:
|
|
82
|
+
"""单组回放:现值 / 档位 / 区间 / 变迁史(规则逐条见模块头)。"""
|
|
83
|
+
value = None
|
|
84
|
+
last_valued = None # 最后**产生当前值**的事件下标(无则 None)
|
|
85
|
+
for i, rec in enumerate(recs):
|
|
86
|
+
if _is_void(rec):
|
|
87
|
+
value = None # 撤回:值作废、无新值
|
|
88
|
+
else:
|
|
89
|
+
value = rec.get("new")
|
|
90
|
+
last_valued = i
|
|
91
|
+
last = recs[-1]
|
|
92
|
+
retired = _is_void(last) # state:最后一个事件的形态决定
|
|
93
|
+
if last_valued is None:
|
|
94
|
+
# 全程无有值事件(孤立撤回):起端无可溯源 ⇒ None(不编造)。
|
|
95
|
+
t_from, seq_from = None, None
|
|
96
|
+
tier = TIER_UNSPECIFIED
|
|
97
|
+
else:
|
|
98
|
+
lv = recs[last_valued]
|
|
99
|
+
t_from, seq_from = lv.get("t"), lv.get("seq")
|
|
100
|
+
tier = lv.get("tier")
|
|
101
|
+
if tier is None:
|
|
102
|
+
tier = TIER_UNSPECIFIED
|
|
103
|
+
# 覆盖者:retired 态=撤回事件(最后一条,闭合点=值作废时刻);否则=有值事件的
|
|
104
|
+
# **下一条**事件(存在则闭合,不存在=None=未闭合);两者共用同一取舍。
|
|
105
|
+
nxt = (len(recs) - 1) if retired else (
|
|
106
|
+
None if last_valued is None else last_valued + 1)
|
|
107
|
+
if nxt is not None and 0 <= nxt < len(recs):
|
|
108
|
+
t_to, seq_to = recs[nxt].get("t"), recs[nxt].get("seq")
|
|
109
|
+
else:
|
|
110
|
+
t_to, seq_to = None, None
|
|
111
|
+
unit = {"subject": subject, "slot": slot, "value": value,
|
|
112
|
+
"state": "retired" if retired else "active",
|
|
113
|
+
"t_from": t_from, "t_to": t_to,
|
|
114
|
+
"seq_from": seq_from, "seq_to": seq_to,
|
|
115
|
+
"tier": tier,
|
|
116
|
+
# 结构在场、恒空:判定口径后置(模块头,不得臆造填充)。
|
|
117
|
+
"alternatives": [], "blindspots": [],
|
|
118
|
+
"event_count": len(recs)}
|
|
119
|
+
if history:
|
|
120
|
+
unit["history"] = [_hist_item(r) for r in recs]
|
|
121
|
+
return unit
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# 生效条件:recs 为台账记录序列(落盘序,可空)时——按 (subject, slot) 分组(组序=首现序)后按 (str(subject), str(slot)) 字典序稳定排序,逐组经 _replay;include_retired 为假时剔除 state=='retired' 的单元,为真时全留;返回单元列表(recs 为空返回 []);history 透传 _replay(假时不带 history 键)。
|
|
125
|
+
def _project(recs, *, include_retired=False, history=True) -> list:
|
|
126
|
+
"""记录序列 → 单元列表(project 与 status 共用的回放单点)。"""
|
|
127
|
+
groups = {}
|
|
128
|
+
for rec in recs:
|
|
129
|
+
key = (rec.get("subject"), rec.get("slot"))
|
|
130
|
+
groups.setdefault(key, []).append(rec)
|
|
131
|
+
# 排序键经 str() 归一:台账是外部可写面(append-only),非字符串 subject/slot 会让
|
|
132
|
+
# 裸元组比较抛 TypeError;字符串形态(正常)下与裸元组字典序逐位一致。
|
|
133
|
+
out = []
|
|
134
|
+
for key in sorted(groups, key=lambda k: (str(k[0]), str(k[1]))):
|
|
135
|
+
unit = _replay(key[0], key[1], groups[key], history=history)
|
|
136
|
+
if unit["state"] == "retired" and not include_retired:
|
|
137
|
+
continue
|
|
138
|
+
out.append(unit)
|
|
139
|
+
return out
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# 生效条件:cg 可解析出 root(经 state_events.ledger_path)时——以 state_events.read(cg, subject=subject, slot=slot) 为源(subject/slot 非 None 才过滤,空串亦为合法过滤值)、现算回放,返回单元列表;include_retired=False 剔除 state=='retired' 的单元;history=False 时单元不带 history 键;台账缺失/为空返回 [](不报错)。**零落盘、零缓存**——同一调用序列中台账追加一条,下一次调用结果即变。
|
|
143
|
+
def project(cg, subject=None, slot=None, include_retired=False,
|
|
144
|
+
history=True) -> list:
|
|
145
|
+
"""槽位寄存器投影(查询时现算):list[dict](单元字段见模块头)。
|
|
146
|
+
|
|
147
|
+
纯读函数:不改台账、不写盘、不缓存;过滤委托 `state_events.read`(单点),
|
|
148
|
+
本模块不另写一套条件解析。
|
|
149
|
+
"""
|
|
150
|
+
recs = state_events.read(cg, subject=subject, slot=slot)
|
|
151
|
+
return _project(recs, include_retired=include_retired, history=history)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# 生效条件:cg 可解析出 root 时——一次 state_events.read(cg) 后返回只读统计 dict:events(台账记录总数)/ subjects(去重 subject 数)/ slots(去重 slot 数)/ retired(回放后 state=='retired' 的单元数)/ bad_rows(state_events.BAD_ROWS 累计值,坏行采样与告警由该模块负责);台账缺失/为空时 events/subjects/slots/retired 皆 0。
|
|
155
|
+
def status(cg) -> dict:
|
|
156
|
+
"""台账与投影的只读读数(不计语义、不改任何状态)。
|
|
157
|
+
|
|
158
|
+
只读一次台账后回放:`bad_rows` 的口径是「累计**跳过动作次数**」(同一坏行每次
|
|
159
|
+
read 均计一次,见 `state_events`)——本函数每次调用只产生**一次** read。
|
|
160
|
+
`slots` 为去重**槽位名**数(同一槽位名挂在多个主体下只算一次);
|
|
161
|
+
`retired` 为回放后退役单元数(值已作废、未复活)。
|
|
162
|
+
"""
|
|
163
|
+
recs = state_events.read(cg)
|
|
164
|
+
units = _project(recs, include_retired=True, history=False)
|
|
165
|
+
return {"events": len(recs),
|
|
166
|
+
"subjects": len({r.get("subject") for r in recs}),
|
|
167
|
+
"slots": len({r.get("slot") for r in recs}),
|
|
168
|
+
"retired": sum(1 for u in units if u["state"] == "retired"),
|
|
169
|
+
"bad_rows": state_events.BAD_ROWS}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# 生效条件:root 给定且 md_cg.mdcos 可导入时返回该 root 上的 MdCGOS 实例(形态同 md_cg/sleep.py:_open_cg);导入失败抛原异常。
|
|
173
|
+
def _open_cg(root: str):
|
|
174
|
+
from .mdcos import MdCGOS
|
|
175
|
+
return MdCGOS(root)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# 生效条件:传入 argv(None 取 sys.argv)——--status 时打印 status(cg) 的 JSON 并返回 0;否则要求 --subject(可配 --slot)或 --slot 单用,打印 project(cg, subject, slot, include_retired, history) 的 JSON 并返回 0;两者皆缺时打印**全量投影**(含退役需显式 --include-retired)并返回 0;--root 缺省取 datapath.mdcg_root()(env MDCG_ROOT);argparse 对未知参数照常以 SystemExit(2) 退出(不吞)。
|
|
179
|
+
def _main(argv=None) -> int:
|
|
180
|
+
"""只读人工入口:台账现状(--status)/ 槽位投影(--subject [--slot])。
|
|
181
|
+
|
|
182
|
+
本入口**恒可用**(不受 `MDCG_STG_STATE` 开关约束——那把开关管的是 MCP 读面);
|
|
183
|
+
全程只读:不 append、不落盘、不改任何状态。
|
|
184
|
+
"""
|
|
185
|
+
ap = argparse.ArgumentParser(
|
|
186
|
+
description="状态槽位投影(只读):台账现算,不落盘、不缓存")
|
|
187
|
+
ap.add_argument("--root", default=None, help="数据根(缺省 mdcg_root())")
|
|
188
|
+
ap.add_argument("--status", action="store_true",
|
|
189
|
+
help="只打印台账/投影统计(events/subjects/slots/retired/bad_rows)")
|
|
190
|
+
ap.add_argument("--subject", default=None, help="按主体过滤(省略=不过滤)")
|
|
191
|
+
ap.add_argument("--slot", default=None, help="按槽位过滤(省略=不过滤)")
|
|
192
|
+
ap.add_argument("--include-retired", dest="include_retired",
|
|
193
|
+
action="store_true", help="含退役单元(缺省剔除)")
|
|
194
|
+
ap.add_argument("--no-history", dest="history", action="store_false",
|
|
195
|
+
help="不带变迁史(history 键省略)")
|
|
196
|
+
a = ap.parse_args(argv)
|
|
197
|
+
try:
|
|
198
|
+
# 进程内调用(守卫/嵌入)会重定向 stdout:无 reconfigure 的流上跳过,
|
|
199
|
+
# 不改变任何输出内容。
|
|
200
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
201
|
+
except Exception: # noqa: BLE001
|
|
202
|
+
pass
|
|
203
|
+
root = os.path.abspath(a.root or _default_root())
|
|
204
|
+
cg = _open_cg(root)
|
|
205
|
+
if a.status:
|
|
206
|
+
out = status(cg)
|
|
207
|
+
else:
|
|
208
|
+
out = project(cg, subject=a.subject, slot=a.slot,
|
|
209
|
+
include_retired=a.include_retired, history=a.history)
|
|
210
|
+
print(json.dumps(out, ensure_ascii=False, indent=2))
|
|
211
|
+
return 0
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# 生效条件:无入参;返回 datapath.mdcg_root()(env MDCG_ROOT 非空时取该值,否则 paths.json/缺省),失败时抛原异常(不吞)。
|
|
215
|
+
def _default_root() -> str:
|
|
216
|
+
from .datapath import mdcg_root
|
|
217
|
+
return mdcg_root()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
if __name__ == "__main__":
|
|
221
|
+
sys.exit(_main())
|