ltcai 5.1.0 → 5.3.0

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.
Files changed (41) hide show
  1. package/README.md +143 -159
  2. package/docs/CHANGELOG.md +72 -2355
  3. package/docs/DEVELOPMENT.md +99 -0
  4. package/docs/LEGACY_COMPATIBILITY.md +55 -0
  5. package/docs/V4_1_VALIDATION_REPORT.md +1 -1
  6. package/docs/V4_3_PRODUCT_HARDENING_REPORT.md +2 -2
  7. package/docs/V4_5_1_VALIDATION_REPORT.md +2 -1
  8. package/docs/WHY_LATTICE.md +4 -3
  9. package/frontend/src/components/FirstRunGuide.tsx +5 -5
  10. package/frontend/src/components/ProductFlow.tsx +1 -1
  11. package/frontend/src/i18n.ts +40 -40
  12. package/frontend/src/pages/Library.tsx +46 -9
  13. package/lattice_brain/__init__.py +1 -1
  14. package/lattice_brain/archive.py +12 -0
  15. package/lattice_brain/portability.py +14 -0
  16. package/lattice_brain/runtime/multi_agent.py +1 -1
  17. package/latticeai/__init__.py +1 -1
  18. package/latticeai/api/marketplace.py +2 -2
  19. package/latticeai/api/models.py +20 -4
  20. package/latticeai/app_factory.py +4 -78
  21. package/latticeai/core/marketplace.py +1 -1
  22. package/latticeai/core/workspace_os.py +18 -4
  23. package/latticeai/runtime/__init__.py +2 -0
  24. package/latticeai/runtime/brain_runtime.py +41 -0
  25. package/latticeai/runtime/config_runtime.py +36 -0
  26. package/latticeai/runtime/security_runtime.py +27 -0
  27. package/latticeai/services/model_capability_registry.py +482 -0
  28. package/latticeai/services/model_catalog.py +99 -96
  29. package/latticeai/services/model_recommendation.py +12 -1
  30. package/package.json +2 -2
  31. package/scripts/verify_hf_model_registry.py +306 -0
  32. package/src-tauri/Cargo.lock +1 -1
  33. package/src-tauri/Cargo.toml +1 -1
  34. package/src-tauri/tauri.conf.json +1 -1
  35. package/static/app/asset-manifest.json +5 -5
  36. package/static/app/assets/index-CQmHhk8Q.css +2 -0
  37. package/static/app/assets/{index-DONOJfMn.js → index-sOXTFUQc.js} +2 -2
  38. package/static/app/assets/index-sOXTFUQc.js.map +1 -0
  39. package/static/app/index.html +2 -2
  40. package/static/app/assets/index-DONOJfMn.js.map +0 -1
  41. package/static/app/assets/index-DuYYT2oh.css +0 -2
@@ -17,6 +17,7 @@ import hashlib
17
17
  import json
18
18
  import os
19
19
  import shutil
20
+ import sqlite3
20
21
  import tempfile
21
22
  import zipfile
22
23
  from datetime import datetime, timezone
@@ -72,6 +73,18 @@ def _sqlite_siblings(db_path: Path) -> tuple[Path, Path, Path]:
72
73
  return (db_path, Path(str(db_path) + "-wal"), Path(str(db_path) + "-shm"))
73
74
 
74
75
 
76
+ def _checkpoint_sqlite(db_path: Path) -> None:
77
+ if not db_path.exists():
78
+ return
79
+ try:
80
+ with sqlite3.connect(str(db_path)) as conn:
81
+ conn.execute("PRAGMA wal_checkpoint(FULL)")
82
+ except sqlite3.Error:
83
+ # Best-effort only. Existing sibling backup/restore still preserves
84
+ # the WAL files if a live connection prevents a checkpoint.
85
+ return
86
+
87
+
75
88
  def _restore_sibling(path: Path, backup: Path) -> None:
76
89
  if backup.exists():
77
90
  shutil.copy2(backup, path)
@@ -85,6 +98,7 @@ def _replace_sqlite_atomically(src: Path, dest: Path, backup_dir: Path) -> None:
85
98
  shutil.copyfile(src, tmp)
86
99
  backups: dict[Path, Path] = {}
87
100
  try:
101
+ _checkpoint_sqlite(dest)
88
102
  # -wal/-shm are transient: another live connection can checkpoint and
89
103
  # remove them between exists() and the copy/unlink. Treat a vanished
90
104
  # sibling as "nothing to preserve" instead of crashing the restore.
@@ -14,7 +14,7 @@ from datetime import datetime
14
14
  from typing import Any, Callable, Dict, List, Optional
15
15
 
16
16
 
17
- MULTI_AGENT_VERSION = "5.1.0"
17
+ MULTI_AGENT_VERSION = "5.3.0"
18
18
 
19
19
  AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
20
20
  CORE_PIPELINE = ("planner", "executor", "reviewer")
@@ -1,3 +1,3 @@
1
1
  """Lattice AI - modular server package."""
2
2
 
3
- __version__ = "5.1.0"
3
+ __version__ = "5.3.0"
@@ -88,7 +88,7 @@ def create_marketplace_router(
88
88
  @router.get("/marketplace/templates/registry")
89
89
  async def template_registry(request: Request):
90
90
  require_user(request)
91
- gate_read(request)
92
- return {"registry": store.list_template_registry()}
91
+ scope = gate_read(request)
92
+ return {"registry": store.list_template_registry(workspace_id=scope)}
93
93
 
94
94
  return router
@@ -425,6 +425,12 @@ def create_models_router(
425
425
  loaded_ids=_router.loaded_model_ids,
426
426
  current_id=_router.current_model_id,
427
427
  )
428
+ # 5.2.0: surface structured registry info (verified status, hf, hardware, strategies) for UX
429
+ try:
430
+ from latticeai.services.model_catalog import get_verified_models
431
+ verified = get_verified_models()
432
+ except Exception:
433
+ verified = []
428
434
  return {
429
435
  "recommended": recommended,
430
436
  "cloud": _router.detected_cloud_models(),
@@ -433,6 +439,12 @@ def create_models_router(
433
439
  "current": _router.current_model_id,
434
440
  "compat_profiles": _list_compat_profiles(),
435
441
  "vision": _vision_capability(_router.current_model_id, engines),
442
+ # 5.2+ transparent model capability registry
443
+ "registry": {
444
+ "version": "5.2.0",
445
+ "verified_count": len(verified),
446
+ "verified": verified[:12], # compact; full via /models/recommendations or future dedicated
447
+ },
436
448
  }
437
449
 
438
450
  @router.get("/models/compat-profiles")
@@ -494,9 +506,8 @@ def create_models_router(
494
506
  async def model_recommendations(request: Request, engine: str = "local_mlx"):
495
507
  """Hardware-aware tri-state model recommendation for this machine.
496
508
 
497
- Detects the system profile (OS/RAM/CPU/GPU/disk) and classifies the
498
- ``engine`` catalog into recommended / compatible / not_recommended,
499
- grouped by family. Used by the onboarding and model-picker UIs.
509
+ 5.2.0: now includes rich capability fields (hf_repo_id, verification,
510
+ hardware, load_strategy, license, safety_notes) from the structured registry.
500
511
  """
501
512
  require_user(request)
502
513
  from auto_setup import probe as auto_setup_probe
@@ -504,6 +515,11 @@ def create_models_router(
504
515
 
505
516
  profile = await asyncio.to_thread(lambda: auto_setup_probe().to_json())
506
517
  catalog = recommend_catalog(profile, engine=engine)
507
- return {"profile": profile, "recommendations": catalog}
518
+ try:
519
+ from latticeai.services.model_catalog import get_verified_models
520
+ reg_meta = {"version": "5.2.0", "verified_total": len(get_verified_models())}
521
+ except Exception:
522
+ reg_meta = {"version": "5.2.0"}
523
+ return {"profile": profile, "recommendations": catalog, "registry": reg_meta}
508
524
 
509
525
  return router
@@ -17,90 +17,16 @@ from __future__ import annotations
17
17
  import threading
18
18
  from typing import TYPE_CHECKING, Any, Dict, List, Optional
19
19
 
20
+ from latticeai.runtime.brain_runtime import build_brain_runtime
21
+ from latticeai.runtime.config_runtime import build_config_runtime
22
+ from latticeai.runtime.security_runtime import build_security_runtime
23
+
20
24
  if TYPE_CHECKING: # imports for annotations only — keep module import light
21
25
  from fastapi import FastAPI
22
26
 
23
27
  from latticeai.core.config import Config
24
28
 
25
29
 
26
- def build_config_runtime(config: "Optional[Config]" = None) -> Dict[str, Any]:
27
- """Build the app configuration runtime without importing heavy model code."""
28
-
29
- from latticeai.core.config import Config
30
-
31
- cfg = config if config is not None else Config.from_env()
32
- return {
33
- "CONFIG": cfg,
34
- "APP_MODE": cfg.app_mode,
35
- "IS_PUBLIC_MODE": cfg.is_public,
36
- "DEFAULT_HOST": cfg.host,
37
- "DEFAULT_PORT": cfg.port,
38
- "NETWORK_EXPOSED": cfg.network_exposed,
39
- "ENABLE_TELEGRAM": cfg.enable_telegram,
40
- "ENABLE_GRAPH": cfg.enable_graph,
41
- "AUTOLOAD_MODELS": cfg.autoload_models,
42
- "MODEL_IDLE_UNLOAD_SECONDS": cfg.model_idle_unload_seconds,
43
- "ALLOW_LOCAL_MODELS": cfg.allow_local_models,
44
- "REQUIRE_AUTH": cfg.require_auth,
45
- "ALLOW_PLAINTEXT_API_KEYS": cfg.allow_plaintext_api_keys,
46
- "CORS_ALLOW_NETWORK": cfg.cors_allow_network,
47
- "CORS_EXTRA_ORIGINS": cfg.cors_extra_origins,
48
- "PUBLIC_MODEL": cfg.public_model,
49
- "LOCAL_MODEL": cfg.local_model,
50
- "LOCAL_DRAFT_MODEL": cfg.local_draft_model,
51
- }
52
-
53
-
54
- def build_security_runtime(config: "Config") -> Dict[str, Any]:
55
- """Build auth/security-derived runtime settings from the central config."""
56
-
57
- from latticeai.core.security import configure_trusted_proxies
58
-
59
- configure_trusted_proxies(config.trusted_proxies)
60
- return {
61
- "SSO_DISCOVERY_URL": config.sso_discovery_url,
62
- "SSO_CLIENT_ID": config.sso_client_id,
63
- "SSO_CLIENT_SECRET": config.sso_client_secret,
64
- "SSO_REDIRECT_URI": config.sso_redirect_uri,
65
- "SSO_PROVIDER_NAME": config.sso_provider_name,
66
- "RATE_LIMIT_ENABLED": config.rate_limit_enabled,
67
- "OPEN_REGISTRATION": config.open_registration,
68
- "INVITE_CODE": config.invite_code,
69
- "INVITE_GATE_ENABLED": config.invite_gate_enabled,
70
- }
71
-
72
-
73
- def build_brain_runtime(
74
- *,
75
- data_dir: Any,
76
- history_file: Any,
77
- enable_graph: bool,
78
- embedder: Any,
79
- storage_engine: Any,
80
- ) -> Dict[str, Any]:
81
- """Construct Brain Core storage/conversation primitives behind one seam."""
82
-
83
- from lattice_brain import BrainCore, ConversationStore
84
-
85
- brain_core = BrainCore.from_paths(
86
- data_dir,
87
- embedder=embedder.provider,
88
- storage_engine=storage_engine,
89
- ) if enable_graph else None
90
- knowledge_graph = brain_core.knowledge if brain_core is not None else None
91
- conversations = (
92
- brain_core.conversations
93
- if brain_core is not None
94
- else ConversationStore(data_dir / "knowledge_graph.sqlite")
95
- )
96
- conversations.import_legacy_json(history_file)
97
- return {
98
- "BRAIN_CORE": brain_core,
99
- "KNOWLEDGE_GRAPH": knowledge_graph,
100
- "CONVERSATIONS": conversations,
101
- }
102
-
103
-
104
30
  def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
105
31
  """The legacy ``server_app`` assembly, moved verbatim into function scope.
106
32
 
@@ -11,7 +11,7 @@ from copy import deepcopy
11
11
  from typing import Any, Dict, List, Optional
12
12
 
13
13
 
14
- MARKETPLACE_VERSION = "5.1.0"
14
+ MARKETPLACE_VERSION = "5.3.0"
15
15
  TEMPLATE_KINDS = ("plugin", "workflow", "agent")
16
16
 
17
17
 
@@ -19,7 +19,7 @@ from pathlib import Path
19
19
  from typing import Any, Callable, Dict, Iterable, List, Optional
20
20
 
21
21
 
22
- WORKSPACE_OS_VERSION = "5.1.0"
22
+ WORKSPACE_OS_VERSION = "5.3.0"
23
23
 
24
24
  # Workspace types separate single-user Personal workspaces from shared
25
25
  # Organization workspaces. Both keep the same local-first JSON store; the type
@@ -2309,8 +2309,22 @@ class WorkspaceOSStore:
2309
2309
  # Marketplace template registry (v2.1 foundation)
2310
2310
  # ------------------------------------------------------------------
2311
2311
 
2312
- def list_template_registry(self) -> Dict[str, Any]:
2313
- return dict(self.load_state().get("template_registry") or {})
2312
+ @staticmethod
2313
+ def _template_registry_key(kind: str, template_id: str, workspace_id: str) -> str:
2314
+ base = f"{kind}:{template_id}"
2315
+ return base if workspace_id == DEFAULT_WORKSPACE_ID else f"{workspace_id}:{base}"
2316
+
2317
+ def list_template_registry(self, workspace_id: Optional[str] = None) -> Dict[str, Any]:
2318
+ state = self.load_state()
2319
+ registry = dict(state.get("template_registry") or {})
2320
+ if workspace_id is None:
2321
+ return registry
2322
+ scope = self._resolve_scope(workspace_id, state)
2323
+ return {
2324
+ key: value
2325
+ for key, value in registry.items()
2326
+ if self._record_workspace(value) == scope
2327
+ }
2314
2328
 
2315
2329
  def mark_template_installed(
2316
2330
  self,
@@ -2323,7 +2337,7 @@ class WorkspaceOSStore:
2323
2337
  ) -> Dict[str, Any]:
2324
2338
  state = self.load_state()
2325
2339
  scope = self._resolve_scope(workspace_id, state)
2326
- key = f"{kind}:{template_id}"
2340
+ key = self._template_registry_key(kind, template_id, scope)
2327
2341
  entry = state.setdefault("template_registry", {}).setdefault(key, {"id": template_id, "kind": kind})
2328
2342
  entry.update({
2329
2343
  "id": template_id,
@@ -0,0 +1,2 @@
1
+ """Runtime assembly seams for the Lattice AI application factory."""
2
+
@@ -0,0 +1,41 @@
1
+ """Brain Core runtime assembly for app startup."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict
6
+
7
+
8
+ def build_brain_runtime(
9
+ *,
10
+ data_dir: Any,
11
+ history_file: Any,
12
+ enable_graph: bool,
13
+ embedder: Any,
14
+ storage_engine: Any,
15
+ ) -> Dict[str, Any]:
16
+ """Construct Brain Core storage/conversation primitives behind one seam."""
17
+
18
+ from lattice_brain import BrainCore, ConversationStore
19
+
20
+ brain_core = (
21
+ BrainCore.from_paths(
22
+ data_dir,
23
+ embedder=embedder.provider,
24
+ storage_engine=storage_engine,
25
+ )
26
+ if enable_graph
27
+ else None
28
+ )
29
+ knowledge_graph = brain_core.knowledge if brain_core is not None else None
30
+ conversations = (
31
+ brain_core.conversations
32
+ if brain_core is not None
33
+ else ConversationStore(data_dir / "knowledge_graph.sqlite")
34
+ )
35
+ conversations.import_legacy_json(history_file)
36
+ return {
37
+ "BRAIN_CORE": brain_core,
38
+ "KNOWLEDGE_GRAPH": knowledge_graph,
39
+ "CONVERSATIONS": conversations,
40
+ }
41
+
@@ -0,0 +1,36 @@
1
+ """Configuration runtime assembly for the FastAPI composition root."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any, Dict, Optional
6
+
7
+ if TYPE_CHECKING:
8
+ from latticeai.core.config import Config
9
+
10
+
11
+ def build_config_runtime(config: "Optional[Config]" = None) -> Dict[str, Any]:
12
+ """Build app configuration values without importing model/runtime code."""
13
+
14
+ from latticeai.core.config import Config
15
+
16
+ cfg = config if config is not None else Config.from_env()
17
+ return {
18
+ "CONFIG": cfg,
19
+ "APP_MODE": cfg.app_mode,
20
+ "IS_PUBLIC_MODE": cfg.is_public,
21
+ "DEFAULT_HOST": cfg.host,
22
+ "DEFAULT_PORT": cfg.port,
23
+ "NETWORK_EXPOSED": cfg.network_exposed,
24
+ "ENABLE_TELEGRAM": cfg.enable_telegram,
25
+ "ENABLE_GRAPH": cfg.enable_graph,
26
+ "AUTOLOAD_MODELS": cfg.autoload_models,
27
+ "MODEL_IDLE_UNLOAD_SECONDS": cfg.model_idle_unload_seconds,
28
+ "ALLOW_LOCAL_MODELS": cfg.allow_local_models,
29
+ "REQUIRE_AUTH": cfg.require_auth,
30
+ "ALLOW_PLAINTEXT_API_KEYS": cfg.allow_plaintext_api_keys,
31
+ "CORS_ALLOW_NETWORK": cfg.cors_allow_network,
32
+ "CORS_EXTRA_ORIGINS": cfg.cors_extra_origins,
33
+ "PUBLIC_MODEL": cfg.public_model,
34
+ "LOCAL_MODEL": cfg.local_model,
35
+ "LOCAL_DRAFT_MODEL": cfg.local_draft_model,
36
+ }
@@ -0,0 +1,27 @@
1
+ """Security runtime assembly for app startup."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any, Dict
6
+
7
+ if TYPE_CHECKING:
8
+ from latticeai.core.config import Config
9
+
10
+
11
+ def build_security_runtime(config: "Config") -> Dict[str, Any]:
12
+ """Build auth/security-derived runtime settings from the central config."""
13
+
14
+ from latticeai.core.security import configure_trusted_proxies
15
+
16
+ configure_trusted_proxies(config.trusted_proxies)
17
+ return {
18
+ "SSO_DISCOVERY_URL": config.sso_discovery_url,
19
+ "SSO_CLIENT_ID": config.sso_client_id,
20
+ "SSO_CLIENT_SECRET": config.sso_client_secret,
21
+ "SSO_REDIRECT_URI": config.sso_redirect_uri,
22
+ "SSO_PROVIDER_NAME": config.sso_provider_name,
23
+ "RATE_LIMIT_ENABLED": config.rate_limit_enabled,
24
+ "OPEN_REGISTRATION": config.open_registration,
25
+ "INVITE_CODE": config.invite_code,
26
+ "INVITE_GATE_ENABLED": config.invite_gate_enabled,
27
+ }