ltcai 6.3.0 → 6.3.1

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.
@@ -26,7 +26,7 @@ from .storage import (
26
26
  storage_from_env,
27
27
  )
28
28
 
29
- __version__ = "6.3.0"
29
+ __version__ = "6.3.1"
30
30
 
31
31
  __all__ = [
32
32
  "AgentRuntime",
@@ -19,7 +19,7 @@ from datetime import datetime
19
19
  from typing import Any, Callable, Dict, List, Optional
20
20
 
21
21
 
22
- MULTI_AGENT_VERSION = "6.3.0"
22
+ MULTI_AGENT_VERSION = "6.3.1"
23
23
 
24
24
  AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
25
25
  CORE_PIPELINE = ("planner", "executor", "reviewer")
@@ -1,3 +1,3 @@
1
1
  """Lattice AI - modular server package."""
2
2
 
3
- __version__ = "6.3.0"
3
+ __version__ = "6.3.1"
@@ -18,6 +18,7 @@ import threading
18
18
  from typing import TYPE_CHECKING, Any, Dict, List, Optional
19
19
 
20
20
  from latticeai.runtime.app_context_runtime import build_app_context
21
+ from latticeai.runtime.access_runtime import build_access_runtime
21
22
  from latticeai.runtime.bootstrap import build_session_runtime
22
23
  from latticeai.runtime.brain_runtime import build_brain_runtime
23
24
  from latticeai.runtime.chat_wiring import (
@@ -122,12 +123,11 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
122
123
  from latticeai.core.enterprise import (
123
124
  capability_registry,
124
125
  )
125
- from latticeai.core.policy import normalize_role, policy_matrix, require_capability
126
+ from latticeai.core.policy import policy_matrix
126
127
  from latticeai.core.users import (
127
128
  ensure_user_identity,
128
129
  load_users_file,
129
130
  migrate_knowledge_graph_identity,
130
- normalize_email,
131
131
  save_users_file,
132
132
  user_id_for_email as _user_id_for_email,
133
133
  )
@@ -813,42 +813,21 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
813
813
  def clear_conversation(conversation_id: str, started_at: Optional[str] = None) -> Dict:
814
814
  return CONVERSATIONS.clear_conversation(conversation_id, started_at=started_at)
815
815
 
816
- def get_user_role(email: str, users: Optional[Dict] = None) -> str:
817
- users = users or load_users()
818
- identity = str(email or "")
819
- normalized_email = normalize_email(identity)
820
- user = users.get(normalized_email) or users.get(identity) or next(
821
- (
822
- item for item in users.values()
823
- if isinstance(item, dict) and item.get("id") == identity
824
- ),
825
- {},
826
- )
827
- if isinstance(user, dict) and user.get("role"):
828
- return normalize_role(user["role"])
829
- admin_emails = {normalize_email(item) for item in CONFIG.admin_emails}
830
- if normalized_email in admin_emails:
831
- return "admin"
832
- first_email = next(iter(users), None)
833
- return "admin" if first_email == normalized_email else "user"
834
-
835
- def _extract_bearer_token(request: Request) -> Optional[str]:
836
- auth = request.headers.get("Authorization", "")
837
- if auth.startswith("Bearer "):
838
- return auth[7:].strip()
839
- return request.cookies.get("session_token")
840
-
841
- def get_current_user(request: Request) -> Optional[str]:
842
- token = _extract_bearer_token(request)
843
- if token:
844
- return get_session_email(token)
845
- return None
846
-
847
- def require_user(request: Request) -> str:
848
- email = get_current_user(request)
849
- if REQUIRE_AUTH and not email:
850
- raise HTTPException(status_code=401, detail="인증이 필요합니다.")
851
- return email or ""
816
+ _access_runtime = build_access_runtime(
817
+ config=CONFIG,
818
+ require_auth=REQUIRE_AUTH,
819
+ http_exception=HTTPException,
820
+ request_type=Request,
821
+ load_users=load_users,
822
+ get_session_email=get_session_email,
823
+ user_id_for_email=_user_id_for_email,
824
+ )
825
+ get_user_role = _access_runtime["get_user_role"]
826
+ _extract_bearer_token = _access_runtime["_extract_bearer_token"]
827
+ get_current_user = _access_runtime["get_current_user"]
828
+ require_user = _access_runtime["require_user"]
829
+ require_admin = _access_runtime["require_admin"]
830
+ public_user = _access_runtime["public_user"]
852
831
 
853
832
 
854
833
  # ── Rate limiting & file validation — delegated to latticeai.core.security ────
@@ -917,35 +896,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
917
896
  raise HTTPException(status_code=403, detail="승인된 파일 내용과 요청 내용이 다릅니다.")
918
897
 
919
898
 
920
- def require_admin(request: Request) -> tuple[str, Dict]:
921
- users = load_users()
922
- if not REQUIRE_AUTH:
923
- return "", users
924
- token = _extract_bearer_token(request)
925
- if token:
926
- email = get_session_email(token)
927
- if email:
928
- role = get_user_role(email, users)
929
- try:
930
- require_capability(role, "admin:users")
931
- return email, users
932
- except PermissionError:
933
- pass
934
- raise HTTPException(status_code=403, detail="관리자 권한이 필요합니다.")
935
-
936
- def public_user(email: str, user: Dict, users: Dict) -> Dict:
937
- role = get_user_role(email, users)
938
- user_id = user.get("id") or _user_id_for_email(users, email)
939
- return {
940
- "id": user_id,
941
- "email": email,
942
- "identity": user_id,
943
- "name": user.get("name", ""),
944
- "nickname": user.get("nickname", ""),
945
- "role": role,
946
- "disabled": bool(user.get("disabled", False)),
947
- }
948
-
949
899
  def get_history_user(email: Optional[str], nickname: Optional[str] = None) -> Dict:
950
900
  if not email:
951
901
  return {"user_email": None, "user_nickname": nickname or None}
@@ -11,7 +11,7 @@ from copy import deepcopy
11
11
  from typing import Any, Dict, List, Optional
12
12
 
13
13
 
14
- MARKETPLACE_VERSION = "6.3.0"
14
+ MARKETPLACE_VERSION = "6.3.1"
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 = "6.3.0"
22
+ WORKSPACE_OS_VERSION = "6.3.1"
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
@@ -0,0 +1,97 @@
1
+ """Access-control helper closures for the app factory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Callable, Dict, Optional
6
+
7
+
8
+ def build_access_runtime(
9
+ *,
10
+ config: Any,
11
+ require_auth: bool,
12
+ http_exception: Any,
13
+ request_type: Any,
14
+ load_users: Callable[[], Dict],
15
+ get_session_email: Callable[[str], Optional[str]],
16
+ user_id_for_email: Callable[[Dict, str], str],
17
+ ) -> Dict[str, Any]:
18
+ """Build user/admin access helpers without changing legacy call signatures."""
19
+
20
+ from latticeai.core.policy import normalize_role, require_capability
21
+ from latticeai.core.users import normalize_email
22
+
23
+ def get_user_role(email: str, users: Optional[Dict] = None) -> str:
24
+ users = users or load_users()
25
+ identity = str(email or "")
26
+ normalized_email = normalize_email(identity)
27
+ user = users.get(normalized_email) or users.get(identity) or next(
28
+ (
29
+ item
30
+ for item in users.values()
31
+ if isinstance(item, dict) and item.get("id") == identity
32
+ ),
33
+ {},
34
+ )
35
+ if isinstance(user, dict) and user.get("role"):
36
+ return normalize_role(user["role"])
37
+ admin_emails = {normalize_email(item) for item in config.admin_emails}
38
+ if normalized_email in admin_emails:
39
+ return "admin"
40
+ first_email = next(iter(users), None)
41
+ return "admin" if first_email == normalized_email else "user"
42
+
43
+ def extract_bearer_token(request: request_type) -> Optional[str]:
44
+ auth = request.headers.get("Authorization", "")
45
+ if auth.startswith("Bearer "):
46
+ return auth[7:].strip()
47
+ return request.cookies.get("session_token")
48
+
49
+ def get_current_user(request: request_type) -> Optional[str]:
50
+ token = extract_bearer_token(request)
51
+ if token:
52
+ return get_session_email(token)
53
+ return None
54
+
55
+ def require_user(request: request_type) -> str:
56
+ email = get_current_user(request)
57
+ if require_auth and not email:
58
+ raise http_exception(status_code=401, detail="인증이 필요합니다.")
59
+ return email or ""
60
+
61
+ def require_admin(request: request_type) -> tuple[str, Dict]:
62
+ users = load_users()
63
+ if not require_auth:
64
+ return "", users
65
+ token = extract_bearer_token(request)
66
+ if token:
67
+ email = get_session_email(token)
68
+ if email:
69
+ role = get_user_role(email, users)
70
+ try:
71
+ require_capability(role, "admin:users")
72
+ return email, users
73
+ except PermissionError:
74
+ pass
75
+ raise http_exception(status_code=403, detail="관리자 권한이 필요합니다.")
76
+
77
+ def public_user(email: str, user: Dict, users: Dict) -> Dict:
78
+ role = get_user_role(email, users)
79
+ user_id = user.get("id") or user_id_for_email(users, email)
80
+ return {
81
+ "id": user_id,
82
+ "email": email,
83
+ "identity": user_id,
84
+ "name": user.get("name", ""),
85
+ "nickname": user.get("nickname", ""),
86
+ "role": role,
87
+ "disabled": bool(user.get("disabled", False)),
88
+ }
89
+
90
+ return {
91
+ "get_user_role": get_user_role,
92
+ "_extract_bearer_token": extract_bearer_token,
93
+ "get_current_user": get_current_user,
94
+ "require_user": require_user,
95
+ "require_admin": require_admin,
96
+ "public_user": public_user,
97
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ltcai",
3
- "version": "6.3.0",
3
+ "version": "6.3.1",
4
4
  "description": "Lattice AI — local-first Digital Brain that keeps your knowledge durable across any AI model.",
5
5
  "homepage": "https://github.com/TaeSooPark-PTS/LatticeAI#readme",
6
6
  "repository": {
@@ -1654,7 +1654,7 @@ dependencies = [
1654
1654
 
1655
1655
  [[package]]
1656
1656
  name = "lattice-ai-desktop"
1657
- version = "6.3.0"
1657
+ version = "6.3.1"
1658
1658
  dependencies = [
1659
1659
  "plist",
1660
1660
  "serde",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "lattice-ai-desktop"
3
- version = "6.3.0"
3
+ version = "6.3.1"
4
4
  description = "Lattice AI Digital Brain desktop shell"
5
5
  authors = ["TaeSoo Park"]
6
6
  edition = "2021"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://schema.tauri.app/config/2",
3
3
  "productName": "Lattice AI",
4
- "version": "6.3.0",
4
+ "version": "6.3.1",
5
5
  "identifier": "ai.lattice.desktop",
6
6
  "build": {
7
7
  "beforeDevCommand": "npm run frontend:dev",
@@ -1,12 +1,12 @@
1
1
  {
2
- "version": "6.3.0",
2
+ "version": "6.3.1",
3
3
  "generated_at": "vite",
4
4
  "entrypoints": {
5
5
  "app": "/static/app/index.html"
6
6
  },
7
7
  "assets": {
8
8
  "../node_modules/@tauri-apps/api/core.js": "/static/app/assets/core-CwxXejkd.js",
9
- "index.html": "/static/app/assets/index-D76dWuQk.js",
9
+ "index.html": "/static/app/assets/index-t1jx1BR9.js",
10
10
  "assets/index-Div5vMlq.css": "/static/app/assets/index-Div5vMlq.css"
11
11
  },
12
12
  "vite": {
@@ -17,7 +17,7 @@
17
17
  "isDynamicEntry": true
18
18
  },
19
19
  "index.html": {
20
- "file": "assets/index-D76dWuQk.js",
20
+ "file": "assets/index-t1jx1BR9.js",
21
21
  "name": "index",
22
22
  "src": "index.html",
23
23
  "isEntry": true,