nexo-brain 7.30.8 → 7.30.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/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -1
- package/package.json +1 -1
- package/src/auto_update.py +21 -0
- package/src/doctor/models.py +1 -0
- package/src/doctor/orchestrator.py +2 -2
- package/src/doctor/providers/boot.py +1 -1
- package/src/doctor/providers/deep.py +1 -1
- package/src/doctor/providers/runtime.py +114 -16
- package/src/plugins/update.py +38 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.30.
|
|
3
|
+
"version": "7.30.10",
|
|
4
4
|
"description": "Local cognitive runtime for Claude Code \u2014 persistent memory, overnight learning, doctor diagnostics, personal scripts, recovery-aware jobs, startup preflight, and optional dashboard/power helper.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "NEXO Brain",
|
package/README.md
CHANGED
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
|
|
19
19
|
[Watch the overview video](https://nexo-brain.com/watch/) · [Watch on YouTube](https://www.youtube.com/watch?v=i2lkGhKyVqI) · [Open the infographic](https://nexo-brain.com/assets/nexo-brain-infographic-v5.png)
|
|
20
20
|
|
|
21
|
-
Version `7.30.
|
|
21
|
+
Version `7.30.10` is the current packaged-runtime line. Patch release over v7.30.9 - packaged `nexo update` now stamps the verified repair baseline after import verification, including same-version maintenance runs.
|
|
22
|
+
|
|
23
|
+
Previously in `7.30.9`: patch release over v7.30.8 - post-update self-heal now stamps a verified repair baseline, and doctor release gates distinguish current installation failures from historical operator/session drift.
|
|
24
|
+
|
|
25
|
+
Previously in `7.30.8`: patch release over v7.30.7 - Deep Sleep now folds parallel Codex sub-agents into their parent thread and Local Context stops the `entity_facts` cartesian blow-up that created runaway sidecar databases.
|
|
22
26
|
|
|
23
27
|
Previously in `7.30.7`: patch release over v7.30.6 - the Deep Sleep retention update is republished with the required release smoke contract so final closeout, npm, GitHub, and runtime verification stay aligned.
|
|
24
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.30.
|
|
3
|
+
"version": "7.30.10",
|
|
4
4
|
"mcpName": "io.github.wazionapps/nexo",
|
|
5
5
|
"description": "NEXO Brain — Shared brain for AI agents. Persistent memory, semantic RAG, natural forgetting, metacognitive guard, trust scoring, 150+ MCP tools. Works with Claude Code, Codex, Claude Desktop & any MCP client. 100% local, free.",
|
|
6
6
|
"homepage": "https://nexo-brain.com",
|
package/src/auto_update.py
CHANGED
|
@@ -70,6 +70,7 @@ DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
70
70
|
SRC_DIR = Path(__file__).resolve().parent
|
|
71
71
|
NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(SRC_DIR)))
|
|
72
72
|
REPO_DIR = SRC_DIR.parent
|
|
73
|
+
REPAIR_BASELINE_FILE = "last-repair-baseline.json"
|
|
73
74
|
|
|
74
75
|
|
|
75
76
|
def _resolve_repo_dir() -> Path:
|
|
@@ -90,6 +91,22 @@ def _resolve_repo_dir() -> Path:
|
|
|
90
91
|
|
|
91
92
|
_RESOLVED_REPO_DIR = _resolve_repo_dir()
|
|
92
93
|
|
|
94
|
+
|
|
95
|
+
def _stamp_runtime_repair_baseline(dest: Path) -> str:
|
|
96
|
+
operations_dir = dest / "operations"
|
|
97
|
+
operations_dir.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
now = time.time()
|
|
99
|
+
payload = {
|
|
100
|
+
"last_repair_epoch": now,
|
|
101
|
+
"last_repair_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(now)),
|
|
102
|
+
"source": "auto_update._run_runtime_post_sync",
|
|
103
|
+
"reason": "verified runtime repair baseline after update/post-sync",
|
|
104
|
+
}
|
|
105
|
+
(operations_dir / REPAIR_BASELINE_FILE).write_text(
|
|
106
|
+
json.dumps(payload, indent=2, ensure_ascii=False) + "\n"
|
|
107
|
+
)
|
|
108
|
+
return "runtime-repair-baseline"
|
|
109
|
+
|
|
93
110
|
LAST_CHECK_FILE = DATA_DIR / "auto_update_last_check.json"
|
|
94
111
|
MIGRATION_VERSION_FILE = DATA_DIR / "migration_version"
|
|
95
112
|
CLAUDE_MD_VERSION_FILE = DATA_DIR / "claude_md_version.txt"
|
|
@@ -5210,6 +5227,10 @@ def _run_runtime_post_sync(dest: Path = NEXO_HOME, progress_fn=None) -> tuple[bo
|
|
|
5210
5227
|
if verify.returncode != 0:
|
|
5211
5228
|
return False, [verify.stderr.strip() or verify.stdout.strip() or "import verify failed"]
|
|
5212
5229
|
actions.append("verify")
|
|
5230
|
+
try:
|
|
5231
|
+
actions.append(_stamp_runtime_repair_baseline(dest))
|
|
5232
|
+
except Exception as exc:
|
|
5233
|
+
actions.append(f"runtime-repair-baseline-warning:{exc.__class__.__name__}")
|
|
5213
5234
|
return True, actions
|
|
5214
5235
|
|
|
5215
5236
|
|
package/src/doctor/models.py
CHANGED
|
@@ -46,7 +46,7 @@ def run_doctor(tier: str = "boot", fix: bool = False, plane: str = "") -> Doctor
|
|
|
46
46
|
report.duration_ms = int((time.monotonic() - start) * 1000)
|
|
47
47
|
return report
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
normalized_plane, preflight = diagnostic_plane_preflight(plane)
|
|
50
50
|
if preflight is not None:
|
|
51
51
|
report.add(preflight)
|
|
52
52
|
report.compute_status()
|
|
@@ -60,7 +60,7 @@ def run_doctor(tier: str = "boot", fix: bool = False, plane: str = "") -> Doctor
|
|
|
60
60
|
if not runner:
|
|
61
61
|
continue
|
|
62
62
|
try:
|
|
63
|
-
checks = runner(fix=fix)
|
|
63
|
+
checks = runner(fix=fix, plane=normalized_plane)
|
|
64
64
|
for check in checks:
|
|
65
65
|
report.add(check)
|
|
66
66
|
except Exception as exc:
|
|
@@ -899,7 +899,7 @@ def check_f06_migration_consistency() -> DoctorCheck:
|
|
|
899
899
|
)
|
|
900
900
|
|
|
901
901
|
|
|
902
|
-
def run_boot_checks(fix: bool = False) -> list[DoctorCheck]:
|
|
902
|
+
def run_boot_checks(fix: bool = False, plane: str = "") -> list[DoctorCheck]:
|
|
903
903
|
"""Run all boot-tier checks."""
|
|
904
904
|
checks = [
|
|
905
905
|
safe_check(check_db_exists),
|
|
@@ -357,7 +357,7 @@ def check_learning_count() -> DoctorCheck:
|
|
|
357
357
|
)
|
|
358
358
|
|
|
359
359
|
|
|
360
|
-
def run_deep_checks(fix: bool = False) -> list[DoctorCheck]:
|
|
360
|
+
def run_deep_checks(fix: bool = False, plane: str = "") -> list[DoctorCheck]:
|
|
361
361
|
"""Run all deep-tier checks. Read-only."""
|
|
362
362
|
return [
|
|
363
363
|
safe_check(check_self_audit_summary),
|
|
@@ -64,6 +64,47 @@ CORE_AUTOMATION_CALLERS_BY_CRON = {
|
|
|
64
64
|
"morning-agent": ("morning_agent",),
|
|
65
65
|
"sleep": ("sleep/nightly",),
|
|
66
66
|
}
|
|
67
|
+
REPAIR_BASELINE_FILE = "last-repair-baseline.json"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _post_update_repair_baseline_epoch() -> float:
|
|
71
|
+
path = paths.operations_dir() / REPAIR_BASELINE_FILE
|
|
72
|
+
if not path.is_file():
|
|
73
|
+
return 0.0
|
|
74
|
+
try:
|
|
75
|
+
payload = json.loads(path.read_text())
|
|
76
|
+
except Exception:
|
|
77
|
+
return 0.0
|
|
78
|
+
if not isinstance(payload, dict):
|
|
79
|
+
return 0.0
|
|
80
|
+
for key in ("last_repair_epoch", "timestamp_epoch"):
|
|
81
|
+
try:
|
|
82
|
+
value = float(payload.get(key) or 0)
|
|
83
|
+
except Exception:
|
|
84
|
+
value = 0.0
|
|
85
|
+
if value > 0:
|
|
86
|
+
return value
|
|
87
|
+
raw_iso = str(payload.get("last_repair_at") or payload.get("timestamp") or "").strip()
|
|
88
|
+
if not raw_iso:
|
|
89
|
+
return 0.0
|
|
90
|
+
try:
|
|
91
|
+
parsed = dt.datetime.fromisoformat(raw_iso.replace("Z", "+00:00"))
|
|
92
|
+
except Exception:
|
|
93
|
+
return 0.0
|
|
94
|
+
if parsed.tzinfo is None:
|
|
95
|
+
parsed = parsed.replace(tzinfo=dt.timezone.utc)
|
|
96
|
+
return parsed.timestamp()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _history_cutoff_epoch(*, days: int) -> float:
|
|
100
|
+
return max(time.time() - (days * 86400), _post_update_repair_baseline_epoch())
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _history_baseline_sqlite() -> str:
|
|
104
|
+
epoch = _post_update_repair_baseline_epoch()
|
|
105
|
+
if epoch <= 0:
|
|
106
|
+
return ""
|
|
107
|
+
return dt.datetime.fromtimestamp(epoch, tz=dt.timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
|
67
108
|
|
|
68
109
|
|
|
69
110
|
def _evolution_objective_payload() -> dict:
|
|
@@ -271,7 +312,7 @@ def _recent_codex_session_parity_status(*, days: int = 7, max_files: int = 24) -
|
|
|
271
312
|
Path.home() / ".codex" / "sessions",
|
|
272
313
|
Path.home() / ".codex" / "archived_sessions",
|
|
273
314
|
]
|
|
274
|
-
cutoff =
|
|
315
|
+
cutoff = _history_cutoff_epoch(days=days)
|
|
275
316
|
candidates: list[tuple[float, Path]] = []
|
|
276
317
|
for root in roots:
|
|
277
318
|
if not root.exists():
|
|
@@ -288,6 +329,7 @@ def _recent_codex_session_parity_status(*, days: int = 7, max_files: int = 24) -
|
|
|
288
329
|
|
|
289
330
|
status = {
|
|
290
331
|
"files": len(files),
|
|
332
|
+
"history_baseline_epoch": _post_update_repair_baseline_epoch(),
|
|
291
333
|
"bootstrap_sessions": 0,
|
|
292
334
|
"startup_sessions": 0,
|
|
293
335
|
"heartbeat_sessions": 0,
|
|
@@ -578,7 +620,7 @@ def _recent_codex_conditioned_file_discipline_status(*, days: int = 7, max_files
|
|
|
578
620
|
Path.home() / ".codex" / "sessions",
|
|
579
621
|
Path.home() / ".codex" / "archived_sessions",
|
|
580
622
|
]
|
|
581
|
-
cutoff =
|
|
623
|
+
cutoff = _history_cutoff_epoch(days=days)
|
|
582
624
|
candidates: list[tuple[float, Path]] = []
|
|
583
625
|
for root in roots:
|
|
584
626
|
if not root.exists():
|
|
@@ -593,6 +635,7 @@ def _recent_codex_conditioned_file_discipline_status(*, days: int = 7, max_files
|
|
|
593
635
|
candidates.sort(key=lambda item: item[0], reverse=True)
|
|
594
636
|
files = candidates[:max_files]
|
|
595
637
|
status["files"] = len(files)
|
|
638
|
+
status["history_baseline_epoch"] = _post_update_repair_baseline_epoch()
|
|
596
639
|
|
|
597
640
|
for file_mtime, path in files:
|
|
598
641
|
cwd = ""
|
|
@@ -2312,6 +2355,7 @@ def check_codex_session_parity() -> DoctorCheck:
|
|
|
2312
2355
|
status="healthy",
|
|
2313
2356
|
severity="info",
|
|
2314
2357
|
summary="Codex session parity check skipped (Codex not selected)",
|
|
2358
|
+
category="operator_history",
|
|
2315
2359
|
)
|
|
2316
2360
|
|
|
2317
2361
|
audit = _recent_codex_session_parity_status()
|
|
@@ -2329,6 +2373,7 @@ def check_codex_session_parity() -> DoctorCheck:
|
|
|
2329
2373
|
"Codex is selected, but there are no recent durable Codex sessions to inspect. "
|
|
2330
2374
|
"NEXO cannot prove that manual Codex sessions are entering the shared-brain startup flow."
|
|
2331
2375
|
),
|
|
2376
|
+
category="operator_history",
|
|
2332
2377
|
)
|
|
2333
2378
|
|
|
2334
2379
|
evidence = [
|
|
@@ -2378,6 +2423,7 @@ def check_codex_session_parity() -> DoctorCheck:
|
|
|
2378
2423
|
"Codex is selected, but recent durable Codex sessions are not consistently showing NEXO bootstrap markers or `nexo_startup`. "
|
|
2379
2424
|
"Manual Codex sessions may still be starting too plain."
|
|
2380
2425
|
) if status != "healthy" else "",
|
|
2426
|
+
category="operator_history",
|
|
2381
2427
|
)
|
|
2382
2428
|
|
|
2383
2429
|
|
|
@@ -2400,6 +2446,7 @@ def check_bootstrap_reached_startup() -> DoctorCheck:
|
|
|
2400
2446
|
status="healthy",
|
|
2401
2447
|
severity="info",
|
|
2402
2448
|
summary="Startup reachability skipped (Codex not selected)",
|
|
2449
|
+
category="operator_history",
|
|
2403
2450
|
)
|
|
2404
2451
|
|
|
2405
2452
|
audit = _recent_codex_session_parity_status(days=1, max_files=48)
|
|
@@ -2425,6 +2472,7 @@ def check_bootstrap_reached_startup() -> DoctorCheck:
|
|
|
2425
2472
|
evidence=evidence,
|
|
2426
2473
|
repair_plan=["Start Codex through the managed NEXO launcher and re-run doctor"],
|
|
2427
2474
|
escalation_prompt="NEXO cannot prove recent Codex sessions reached startup.",
|
|
2475
|
+
category="operator_history",
|
|
2428
2476
|
)
|
|
2429
2477
|
|
|
2430
2478
|
status = "healthy" if missing == 0 else "critical"
|
|
@@ -2447,6 +2495,7 @@ def check_bootstrap_reached_startup() -> DoctorCheck:
|
|
|
2447
2495
|
escalation_prompt=(
|
|
2448
2496
|
"Codex sessions are starting without the shared-brain startup step, so memory/guard continuity is not guaranteed."
|
|
2449
2497
|
) if status != "healthy" else "",
|
|
2498
|
+
category="operator_history",
|
|
2450
2499
|
)
|
|
2451
2500
|
|
|
2452
2501
|
|
|
@@ -2468,6 +2517,7 @@ def check_codex_conditioned_file_discipline() -> DoctorCheck:
|
|
|
2468
2517
|
status="healthy",
|
|
2469
2518
|
severity="info",
|
|
2470
2519
|
summary="Codex conditioned-file discipline check skipped (Codex not selected)",
|
|
2520
|
+
category="operator_history",
|
|
2471
2521
|
)
|
|
2472
2522
|
|
|
2473
2523
|
audit = _recent_codex_conditioned_file_discipline_status()
|
|
@@ -2491,6 +2541,7 @@ def check_codex_conditioned_file_discipline() -> DoctorCheck:
|
|
|
2491
2541
|
severity="info",
|
|
2492
2542
|
summary="No active conditioned-file learnings defined for Codex session audits",
|
|
2493
2543
|
evidence=evidence,
|
|
2544
|
+
category="operator_history",
|
|
2494
2545
|
)
|
|
2495
2546
|
|
|
2496
2547
|
if audit["files"] == 0 or audit["conditioned_sessions"] == 0:
|
|
@@ -2501,6 +2552,7 @@ def check_codex_conditioned_file_discipline() -> DoctorCheck:
|
|
|
2501
2552
|
severity="info",
|
|
2502
2553
|
summary="No conditioned-file touches seen in recent Codex sessions",
|
|
2503
2554
|
evidence=evidence + [f"conditioned touches: {audit['conditioned_touches']}"],
|
|
2555
|
+
category="operator_history",
|
|
2504
2556
|
)
|
|
2505
2557
|
|
|
2506
2558
|
evidence.extend([
|
|
@@ -2595,10 +2647,11 @@ def check_codex_conditioned_file_discipline() -> DoctorCheck:
|
|
|
2595
2647
|
"Codex sessions are touching conditioned files without the expected protocol/guard sequence. "
|
|
2596
2648
|
"Until this is clean, parity with Claude hooks is still incomplete."
|
|
2597
2649
|
) if status != "healthy" else "",
|
|
2650
|
+
category="operator_history",
|
|
2598
2651
|
)
|
|
2599
2652
|
|
|
2600
2653
|
|
|
2601
|
-
def check_codex_protocol_compliance() -> DoctorCheck:
|
|
2654
|
+
def check_codex_protocol_compliance(include_history: bool = True) -> DoctorCheck:
|
|
2602
2655
|
try:
|
|
2603
2656
|
schedule = _load_json(SCHEDULE_FILE) if SCHEDULE_FILE.is_file() else {}
|
|
2604
2657
|
except Exception:
|
|
@@ -2646,6 +2699,16 @@ def check_codex_protocol_compliance() -> DoctorCheck:
|
|
|
2646
2699
|
),
|
|
2647
2700
|
)
|
|
2648
2701
|
|
|
2702
|
+
if not include_history:
|
|
2703
|
+
return DoctorCheck(
|
|
2704
|
+
id="installation_live.codex_protocol_compliance",
|
|
2705
|
+
tier="runtime",
|
|
2706
|
+
status="healthy",
|
|
2707
|
+
severity="info",
|
|
2708
|
+
summary="Codex live protocol enforcement is installed",
|
|
2709
|
+
evidence=[f"codex PreToolUse hook: managed ({hooks.get('pretool_matcher') or '*'})"],
|
|
2710
|
+
)
|
|
2711
|
+
|
|
2649
2712
|
startup = _recent_codex_session_parity_status(days=1)
|
|
2650
2713
|
conditioned = _recent_codex_conditioned_file_discipline_status(days=1)
|
|
2651
2714
|
sessions = int(startup.get("files") or conditioned.get("files") or 0)
|
|
@@ -2659,6 +2722,7 @@ def check_codex_protocol_compliance() -> DoctorCheck:
|
|
|
2659
2722
|
repair_plan=[
|
|
2660
2723
|
"Run Codex through the managed NEXO bootstrap so doctor can verify live protocol compliance",
|
|
2661
2724
|
],
|
|
2725
|
+
category="operator_history",
|
|
2662
2726
|
)
|
|
2663
2727
|
|
|
2664
2728
|
startup_violation_sessions = 0
|
|
@@ -2709,6 +2773,7 @@ def check_codex_protocol_compliance() -> DoctorCheck:
|
|
|
2709
2773
|
escalation_prompt=(
|
|
2710
2774
|
"Codex CLI parity is not clean: recent sessions miss startup/heartbeat or bypass conditioned-file guard discipline."
|
|
2711
2775
|
) if status != "healthy" else "",
|
|
2776
|
+
category="operator_history",
|
|
2712
2777
|
)
|
|
2713
2778
|
|
|
2714
2779
|
|
|
@@ -2898,11 +2963,14 @@ def check_protocol_compliance() -> DoctorCheck:
|
|
|
2898
2963
|
debt_rows = None
|
|
2899
2964
|
if {"protocol_tasks", "protocol_debt"}.issubset(tables):
|
|
2900
2965
|
window = "-7 days"
|
|
2966
|
+
history_floor = _history_baseline_sqlite()
|
|
2967
|
+
task_floor_clause = " AND opened_at >= ?" if history_floor else ""
|
|
2968
|
+
task_params = (window, history_floor) if history_floor else (window,)
|
|
2901
2969
|
tasks = conn.execute(
|
|
2902
|
-
"""SELECT * FROM protocol_tasks
|
|
2903
|
-
WHERE opened_at >= datetime('now', ?)
|
|
2970
|
+
f"""SELECT * FROM protocol_tasks
|
|
2971
|
+
WHERE opened_at >= datetime('now', ?){task_floor_clause}
|
|
2904
2972
|
ORDER BY opened_at DESC""",
|
|
2905
|
-
|
|
2973
|
+
task_params,
|
|
2906
2974
|
).fetchall()
|
|
2907
2975
|
protocol_debt_cols = {
|
|
2908
2976
|
row["name"] for row in conn.execute("PRAGMA table_info(protocol_debt)").fetchall()
|
|
@@ -2946,6 +3014,8 @@ def check_protocol_compliance() -> DoctorCheck:
|
|
|
2946
3014
|
task_status_expr = "'' AS task_status"
|
|
2947
3015
|
if "status" in protocol_task_cols:
|
|
2948
3016
|
task_status_expr = "pt.status AS task_status"
|
|
3017
|
+
debt_floor_clause = " AND pd.created_at >= ?" if history_floor else ""
|
|
3018
|
+
debt_params = (window, history_floor) if history_floor else (window,)
|
|
2949
3019
|
open_debts = conn.execute(
|
|
2950
3020
|
f"""SELECT
|
|
2951
3021
|
pd.severity,
|
|
@@ -2956,9 +3026,9 @@ def check_protocol_compliance() -> DoctorCheck:
|
|
|
2956
3026
|
{task_status_expr}
|
|
2957
3027
|
FROM protocol_debt pd
|
|
2958
3028
|
LEFT JOIN protocol_tasks pt ON pt.task_id = pd.task_id
|
|
2959
|
-
WHERE pd.status = 'open' AND pd.created_at >= datetime('now', ?)
|
|
3029
|
+
WHERE pd.status = 'open' AND pd.created_at >= datetime('now', ?){debt_floor_clause}
|
|
2960
3030
|
ORDER BY pd.created_at DESC""",
|
|
2961
|
-
|
|
3031
|
+
debt_params,
|
|
2962
3032
|
).fetchall()
|
|
2963
3033
|
debt_counter: dict[tuple[str, str], int] = {}
|
|
2964
3034
|
for row in open_debts:
|
|
@@ -2987,12 +3057,13 @@ def check_protocol_compliance() -> DoctorCheck:
|
|
|
2987
3057
|
else:
|
|
2988
3058
|
debt_rows = [
|
|
2989
3059
|
dict(row) for row in conn.execute(
|
|
2990
|
-
"""SELECT severity, debt_type, COUNT(*) AS total
|
|
3060
|
+
f"""SELECT severity, debt_type, COUNT(*) AS total
|
|
2991
3061
|
FROM protocol_debt
|
|
2992
3062
|
WHERE status = 'open' AND created_at >= datetime('now', ?)
|
|
3063
|
+
{"AND created_at >= ?" if history_floor else ""}
|
|
2993
3064
|
GROUP BY severity, debt_type
|
|
2994
3065
|
ORDER BY total DESC, debt_type ASC""",
|
|
2995
|
-
(window,),
|
|
3066
|
+
(window, history_floor) if history_floor else (window,),
|
|
2996
3067
|
).fetchall()
|
|
2997
3068
|
]
|
|
2998
3069
|
has_cortex_evaluations = bool(
|
|
@@ -3010,16 +3081,33 @@ def check_protocol_compliance() -> DoctorCheck:
|
|
|
3010
3081
|
).fetchall()
|
|
3011
3082
|
}
|
|
3012
3083
|
first_eval_row = conn.execute(
|
|
3013
|
-
"""SELECT MIN(created_at) AS first_eval
|
|
3084
|
+
f"""SELECT MIN(created_at) AS first_eval
|
|
3014
3085
|
FROM cortex_evaluations
|
|
3015
|
-
WHERE created_at >= datetime('now', ?)
|
|
3016
|
-
|
|
3086
|
+
WHERE created_at >= datetime('now', ?)
|
|
3087
|
+
{"AND created_at >= ?" if history_floor else ""}""",
|
|
3088
|
+
(window, history_floor) if history_floor else (window,),
|
|
3017
3089
|
).fetchone()
|
|
3018
3090
|
if first_eval_row and first_eval_row["first_eval"]:
|
|
3019
3091
|
first_cortex_eval_at = str(first_eval_row["first_eval"])
|
|
3020
3092
|
finally:
|
|
3021
3093
|
conn.close()
|
|
3022
3094
|
|
|
3095
|
+
history_floor = _history_baseline_sqlite()
|
|
3096
|
+
if tasks is not None and debt_rows is not None and not tasks and not debt_rows and history_floor:
|
|
3097
|
+
return DoctorCheck(
|
|
3098
|
+
id="runtime.protocol_compliance",
|
|
3099
|
+
tier="runtime",
|
|
3100
|
+
status="healthy",
|
|
3101
|
+
severity="info",
|
|
3102
|
+
summary="No protocol drift after the last verified runtime repair",
|
|
3103
|
+
evidence=[
|
|
3104
|
+
"live protocol window: 7d",
|
|
3105
|
+
f"post-update repair baseline: {history_floor}",
|
|
3106
|
+
"no protocol tasks or open debt after repair baseline",
|
|
3107
|
+
],
|
|
3108
|
+
category="operator_history",
|
|
3109
|
+
)
|
|
3110
|
+
|
|
3023
3111
|
if tasks is not None and debt_rows is not None and (tasks or debt_rows):
|
|
3024
3112
|
closed_tasks = [row for row in tasks if row["status"] != "open"]
|
|
3025
3113
|
verify_required = [row for row in closed_tasks if row["must_verify"] and row["status"] == "done"]
|
|
@@ -3122,6 +3210,7 @@ def check_protocol_compliance() -> DoctorCheck:
|
|
|
3122
3210
|
escalation_prompt=(
|
|
3123
3211
|
"Task discipline is drifting in live runtime data. NEXO is still skipping verification, change logging, or correction capture."
|
|
3124
3212
|
) if status != "healthy" else "",
|
|
3213
|
+
category="operator_history",
|
|
3125
3214
|
)
|
|
3126
3215
|
except Exception:
|
|
3127
3216
|
pass
|
|
@@ -3140,6 +3229,7 @@ def check_protocol_compliance() -> DoctorCheck:
|
|
|
3140
3229
|
escalation_prompt=(
|
|
3141
3230
|
"NEXO cannot verify heartbeat / guard_check / change_log compliance because the latest weekly Deep Sleep summary is missing."
|
|
3142
3231
|
),
|
|
3232
|
+
category="operator_history",
|
|
3143
3233
|
)
|
|
3144
3234
|
|
|
3145
3235
|
protocol = summary.get("protocol_summary") or {}
|
|
@@ -3197,6 +3287,7 @@ def check_protocol_compliance() -> DoctorCheck:
|
|
|
3197
3287
|
escalation_prompt=(
|
|
3198
3288
|
"Heartbeat / guard_check / change_log discipline is drifting. NEXO is at risk of repeating known errors and hiding change history."
|
|
3199
3289
|
) if status != "healthy" else "",
|
|
3290
|
+
category="operator_history",
|
|
3200
3291
|
)
|
|
3201
3292
|
|
|
3202
3293
|
|
|
@@ -3979,9 +4070,15 @@ def check_memory_fabric_health(fix: bool = False) -> DoctorCheck:
|
|
|
3979
4070
|
)
|
|
3980
4071
|
|
|
3981
4072
|
|
|
3982
|
-
def
|
|
4073
|
+
def _filter_runtime_checks_for_plane(checks: list[DoctorCheck], plane: str = "") -> list[DoctorCheck]:
|
|
4074
|
+
if plane == "installation_live":
|
|
4075
|
+
return [check for check in checks if check.category != "operator_history"]
|
|
4076
|
+
return checks
|
|
4077
|
+
|
|
4078
|
+
|
|
4079
|
+
def run_runtime_checks(fix: bool = False, plane: str = "") -> list[DoctorCheck]:
|
|
3983
4080
|
"""Run all runtime-tier checks. Read-only by default."""
|
|
3984
|
-
|
|
4081
|
+
checks = [
|
|
3985
4082
|
safe_check(check_immune_status),
|
|
3986
4083
|
safe_check(check_watchdog_status),
|
|
3987
4084
|
safe_check(check_runner_health_status),
|
|
@@ -3992,7 +4089,7 @@ def run_runtime_checks(fix: bool = False) -> list[DoctorCheck]:
|
|
|
3992
4089
|
safe_check(check_codex_session_parity),
|
|
3993
4090
|
safe_check(check_bootstrap_reached_startup),
|
|
3994
4091
|
safe_check(check_codex_conditioned_file_discipline),
|
|
3995
|
-
safe_check(check_codex_protocol_compliance),
|
|
4092
|
+
safe_check(check_codex_protocol_compliance, include_history=plane != "installation_live"),
|
|
3996
4093
|
safe_check(check_claude_desktop_shared_brain),
|
|
3997
4094
|
safe_check(check_transcript_source_parity),
|
|
3998
4095
|
safe_check(check_client_assumption_regressions),
|
|
@@ -4009,3 +4106,4 @@ def run_runtime_checks(fix: bool = False) -> list[DoctorCheck]:
|
|
|
4009
4106
|
safe_check(check_personal_script_registry, fix=fix),
|
|
4010
4107
|
safe_check(check_skill_health, fix=fix),
|
|
4011
4108
|
]
|
|
4109
|
+
return _filter_runtime_checks_for_plane(checks, plane=plane)
|
package/src/plugins/update.py
CHANGED
|
@@ -152,6 +152,23 @@ NEXO_HOME = export_resolved_nexo_home()
|
|
|
152
152
|
DATA_DIR = paths.data_dir()
|
|
153
153
|
BACKUP_BASE = paths.backups_dir()
|
|
154
154
|
TECHNICAL_BACKUP_KEEP = 5
|
|
155
|
+
REPAIR_BASELINE_FILE = "last-repair-baseline.json"
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _stamp_runtime_repair_baseline(source: str = "plugins.update") -> str:
|
|
159
|
+
operations_dir = NEXO_HOME / "operations"
|
|
160
|
+
operations_dir.mkdir(parents=True, exist_ok=True)
|
|
161
|
+
now = time.time()
|
|
162
|
+
payload = {
|
|
163
|
+
"last_repair_epoch": now,
|
|
164
|
+
"last_repair_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(now)),
|
|
165
|
+
"source": source,
|
|
166
|
+
"reason": "verified runtime repair baseline after update/post-sync",
|
|
167
|
+
}
|
|
168
|
+
(operations_dir / REPAIR_BASELINE_FILE).write_text(
|
|
169
|
+
json.dumps(payload, indent=2, ensure_ascii=False) + "\n"
|
|
170
|
+
)
|
|
171
|
+
return "runtime-repair-baseline"
|
|
155
172
|
|
|
156
173
|
|
|
157
174
|
def _env_int(name: str, default: int) -> int:
|
|
@@ -1454,6 +1471,13 @@ def _handle_packaged_update(progress_fn=None, *, include_clis: bool = True) -> s
|
|
|
1454
1471
|
verify_err = _verify_import()
|
|
1455
1472
|
if verify_err:
|
|
1456
1473
|
errors.append(f"verification: {verify_err}")
|
|
1474
|
+
repair_baseline_warning = None
|
|
1475
|
+
if not verify_err:
|
|
1476
|
+
try:
|
|
1477
|
+
_emit_progress(progress_fn, "Stamping runtime repair baseline...")
|
|
1478
|
+
_stamp_runtime_repair_baseline("plugins.update._handle_packaged_update")
|
|
1479
|
+
except Exception as exc:
|
|
1480
|
+
repair_baseline_warning = f"{exc.__class__.__name__}: {exc}"
|
|
1457
1481
|
|
|
1458
1482
|
hook_sync_warning = None
|
|
1459
1483
|
cron_sync_warning = None
|
|
@@ -1616,6 +1640,10 @@ def _handle_packaged_update(progress_fn=None, *, include_clis: bool = True) -> s
|
|
|
1616
1640
|
lines.append(" Clients: configured client targets synced")
|
|
1617
1641
|
else:
|
|
1618
1642
|
lines.append(f" WARNING: client sync: {client_sync_warning}")
|
|
1643
|
+
if not repair_baseline_warning:
|
|
1644
|
+
lines.append(" Repair baseline: updated")
|
|
1645
|
+
else:
|
|
1646
|
+
lines.append(f" WARNING: repair baseline: {repair_baseline_warning}")
|
|
1619
1647
|
if launchagent_reload_summary and launchagent_reload_summary.get("scanned"):
|
|
1620
1648
|
if not launchagent_reload_warning:
|
|
1621
1649
|
lines.append(
|
|
@@ -1765,6 +1793,12 @@ def handle_update(
|
|
|
1765
1793
|
if verify_err:
|
|
1766
1794
|
raise RuntimeError(f"Verification failed: {verify_err}")
|
|
1767
1795
|
steps_done.append("verify")
|
|
1796
|
+
try:
|
|
1797
|
+
_emit_progress(progress_fn, "Stamping runtime repair baseline...")
|
|
1798
|
+
_stamp_runtime_repair_baseline("plugins.update.handle_update")
|
|
1799
|
+
steps_done.append("runtime-repair-baseline")
|
|
1800
|
+
except Exception as e:
|
|
1801
|
+
steps_done.append(f"runtime-repair-baseline-warning:{e.__class__.__name__}")
|
|
1768
1802
|
|
|
1769
1803
|
# Step 8: Sync crons with manifest
|
|
1770
1804
|
cron_sync_result = ""
|
|
@@ -1929,6 +1963,8 @@ def handle_update(
|
|
|
1929
1963
|
trailing.insert(2 if len(trailing) >= 2 else len(trailing), " Crons: synced with manifest")
|
|
1930
1964
|
if "client-sync" in steps_done:
|
|
1931
1965
|
trailing.append(" Clients: configured client targets synced")
|
|
1966
|
+
if "runtime-repair-baseline" in steps_done:
|
|
1967
|
+
trailing.append(" Repair baseline: updated")
|
|
1932
1968
|
if trailing:
|
|
1933
1969
|
msg += "\n" + "\n".join(trailing)
|
|
1934
1970
|
return msg
|
|
@@ -1954,6 +1990,8 @@ def handle_update(
|
|
|
1954
1990
|
lines.extend(external_cli_lines)
|
|
1955
1991
|
if "client-sync" in steps_done:
|
|
1956
1992
|
lines.append(" Clients: configured client targets synced")
|
|
1993
|
+
if "runtime-repair-baseline" in steps_done:
|
|
1994
|
+
lines.append(" Repair baseline: updated")
|
|
1957
1995
|
if versioned_runtime_summary and versioned_runtime_summary.get("ok"):
|
|
1958
1996
|
lines.append(f" Runtime activation: core/current -> versions/{new_version}")
|
|
1959
1997
|
if version_prune_summary and version_prune_summary.get("pruned"):
|