ltcai 4.0.1 → 4.2.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.
- package/README.md +33 -24
- package/desktop/electron/main.cjs +44 -0
- package/docs/CHANGELOG.md +84 -0
- package/docs/V4_1_FRONTEND_ARCHITECTURE_REVIEW.md +65 -0
- package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +70 -0
- package/docs/V4_1_VALIDATION_REPORT.md +47 -0
- package/docs/V4_2_BRAIN_CORE_ARCHITECTURE.md +97 -0
- package/docs/V4_2_STORAGE_MIGRATION_REPORT.md +91 -0
- package/docs/V4_2_VALIDATION_REPORT.md +89 -0
- package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +31 -26
- package/frontend/index.html +24 -0
- package/frontend/openapi.json +14436 -0
- package/frontend/src/App.tsx +184 -0
- package/frontend/src/api/client.ts +320 -0
- package/frontend/src/api/openapi.ts +16921 -0
- package/frontend/src/components/primitives.tsx +204 -0
- package/frontend/src/components/ui/badge.tsx +27 -0
- package/frontend/src/components/ui/button.tsx +37 -0
- package/frontend/src/components/ui/card.tsx +22 -0
- package/frontend/src/components/ui/input.tsx +16 -0
- package/frontend/src/components/ui/textarea.tsx +16 -0
- package/frontend/src/lib/utils.ts +33 -0
- package/frontend/src/main.tsx +23 -0
- package/frontend/src/pages/Act.tsx +245 -0
- package/frontend/src/pages/Ask.tsx +200 -0
- package/frontend/src/pages/Brain.tsx +267 -0
- package/frontend/src/pages/Capture.tsx +158 -0
- package/frontend/src/pages/Library.tsx +187 -0
- package/frontend/src/pages/System.tsx +378 -0
- package/frontend/src/routes.ts +85 -0
- package/frontend/src/store/appStore.ts +54 -0
- package/frontend/src/styles.css +107 -0
- package/kg_schema.py +1 -1
- package/knowledge_graph.py +4 -4
- package/lattice_brain/__init__.py +70 -0
- package/lattice_brain/_kg_common.py +1 -0
- package/lattice_brain/archive.py +133 -0
- package/lattice_brain/context.py +3 -0
- package/lattice_brain/conversations.py +3 -0
- package/lattice_brain/core.py +82 -0
- package/lattice_brain/discovery.py +1 -0
- package/lattice_brain/documents.py +1 -0
- package/lattice_brain/embeddings.py +82 -0
- package/lattice_brain/identity.py +13 -0
- package/lattice_brain/ingest.py +1 -0
- package/lattice_brain/memory.py +3 -0
- package/lattice_brain/network.py +1 -0
- package/lattice_brain/projection.py +1 -0
- package/lattice_brain/provenance.py +1 -0
- package/lattice_brain/retrieval.py +1 -0
- package/lattice_brain/schema.py +1 -0
- package/lattice_brain/storage/__init__.py +22 -0
- package/lattice_brain/storage/base.py +72 -0
- package/lattice_brain/storage/docker.py +105 -0
- package/lattice_brain/storage/factory.py +31 -0
- package/lattice_brain/storage/migration.py +190 -0
- package/lattice_brain/storage/postgres.py +123 -0
- package/lattice_brain/storage/sqlite.py +128 -0
- package/lattice_brain/store.py +3 -0
- package/lattice_brain/write_master.py +1 -0
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/portability.py +69 -0
- package/latticeai/api/setup.py +5 -4
- package/latticeai/api/static_routes.py +4 -4
- package/latticeai/app_factory.py +17 -10
- package/latticeai/brain/__init__.py +6 -6
- package/latticeai/brain/_kg_common.py +1 -1
- package/latticeai/brain/network.py +1 -1
- package/latticeai/brain/retrieval.py +15 -0
- package/latticeai/brain/store.py +22 -6
- package/latticeai/core/config.py +8 -0
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/multi_agent.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/services/kg_portability.py +82 -1
- package/package.json +55 -15
- package/scripts/build_frontend_assets.mjs +38 -0
- package/scripts/bump_version.py +4 -1
- package/scripts/export_openapi.py +31 -0
- package/scripts/lint_frontend.mjs +91 -0
- package/scripts/migrate_brain_storage.py +53 -0
- package/scripts/run_python.mjs +47 -0
- package/scripts/wheel_smoke.py +3 -0
- package/src-tauri/Cargo.lock +4833 -0
- package/src-tauri/Cargo.toml +19 -0
- package/src-tauri/build.rs +3 -0
- package/src-tauri/capabilities/default.json +7 -0
- package/src-tauri/src/main.rs +78 -0
- package/src-tauri/tauri.conf.json +39 -0
- package/static/app/asset-manifest.json +32 -0
- package/static/app/assets/core-CwxXejkd.js +2 -0
- package/static/app/assets/core-CwxXejkd.js.map +1 -0
- package/static/app/assets/index-CDjiH_se.css +2 -0
- package/static/app/assets/index-C_HAkbAg.js +333 -0
- package/static/app/assets/index-C_HAkbAg.js.map +1 -0
- package/static/app/index.html +25 -0
- package/static/manifest.json +2 -2
- package/static/sw.js +4 -4
- package/scripts/build_v3_assets.mjs +0 -170
- package/scripts/lint_v3.mjs +0 -120
- package/static/v3/asset-manifest.json +0 -63
- package/static/v3/css/lattice.base.49deefb5.css +0 -128
- package/static/v3/css/lattice.base.css +0 -128
- package/static/v3/css/lattice.components.cde18231.css +0 -472
- package/static/v3/css/lattice.components.css +0 -472
- package/static/v3/css/lattice.shell.29d36d85.css +0 -452
- package/static/v3/css/lattice.shell.css +0 -452
- package/static/v3/css/lattice.tokens.304cbc40.css +0 -135
- package/static/v3/css/lattice.tokens.css +0 -135
- package/static/v3/css/lattice.views.0a18b6c5.css +0 -360
- package/static/v3/css/lattice.views.css +0 -360
- package/static/v3/index.html +0 -68
- package/static/v3/js/app.c5c80c46.js +0 -26
- package/static/v3/js/app.js +0 -26
- package/static/v3/js/core/api.ba0fbf14.js +0 -625
- package/static/v3/js/core/api.js +0 -625
- package/static/v3/js/core/components.f25b3b93.js +0 -230
- package/static/v3/js/core/components.js +0 -230
- package/static/v3/js/core/dom.a2773eb0.js +0 -148
- package/static/v3/js/core/dom.js +0 -148
- package/static/v3/js/core/i18n.880e1fec.js +0 -575
- package/static/v3/js/core/i18n.js +0 -575
- package/static/v3/js/core/router.584570f2.js +0 -37
- package/static/v3/js/core/router.js +0 -37
- package/static/v3/js/core/routes.37522821.js +0 -101
- package/static/v3/js/core/routes.js +0 -101
- package/static/v3/js/core/shell.e3f6bbfa.js +0 -420
- package/static/v3/js/core/shell.js +0 -420
- package/static/v3/js/core/store.7b2aa044.js +0 -123
- package/static/v3/js/core/store.js +0 -123
- package/static/v3/js/views/account.eff40715.js +0 -143
- package/static/v3/js/views/account.js +0 -143
- package/static/v3/js/views/activity.0d271ef9.js +0 -67
- package/static/v3/js/views/activity.js +0 -67
- package/static/v3/js/views/admin-audit.660a1fb1.js +0 -185
- package/static/v3/js/views/admin-audit.js +0 -185
- package/static/v3/js/views/admin-permissions.a7ae5f09.js +0 -177
- package/static/v3/js/views/admin-permissions.js +0 -177
- package/static/v3/js/views/admin-policies.3658fd86.js +0 -102
- package/static/v3/js/views/admin-policies.js +0 -102
- package/static/v3/js/views/admin-private-vpc.7d342d36.js +0 -135
- package/static/v3/js/views/admin-private-vpc.js +0 -135
- package/static/v3/js/views/admin-security.07c66b72.js +0 -180
- package/static/v3/js/views/admin-security.js +0 -180
- package/static/v3/js/views/admin-users.f7ac7b43.js +0 -166
- package/static/v3/js/views/admin-users.js +0 -166
- package/static/v3/js/views/agents.17c5288d.js +0 -564
- package/static/v3/js/views/agents.js +0 -564
- package/static/v3/js/views/chat.e250e2cc.js +0 -624
- package/static/v3/js/views/chat.js +0 -624
- package/static/v3/js/views/files.adad14c1.js +0 -365
- package/static/v3/js/views/files.js +0 -365
- package/static/v3/js/views/graph-canvas.17c15d65.js +0 -509
- package/static/v3/js/views/graph-canvas.js +0 -509
- package/static/v3/js/views/home.24f8b8ae.js +0 -200
- package/static/v3/js/views/home.js +0 -200
- package/static/v3/js/views/hooks.37895880.js +0 -220
- package/static/v3/js/views/hooks.js +0 -220
- package/static/v3/js/views/hybrid-search.2fb63ed9.js +0 -194
- package/static/v3/js/views/hybrid-search.js +0 -194
- package/static/v3/js/views/knowledge-graph.4d09c537.js +0 -529
- package/static/v3/js/views/knowledge-graph.js +0 -529
- package/static/v3/js/views/marketplace.ab0583d4.js +0 -141
- package/static/v3/js/views/marketplace.js +0 -141
- package/static/v3/js/views/mcp.99b5c6a7.js +0 -114
- package/static/v3/js/views/mcp.js +0 -114
- package/static/v3/js/views/memory.4ebdf474.js +0 -147
- package/static/v3/js/views/memory.js +0 -147
- package/static/v3/js/views/models.a1ffa147.js +0 -256
- package/static/v3/js/views/models.js +0 -256
- package/static/v3/js/views/my-computer.d9d9ae1c.js +0 -463
- package/static/v3/js/views/my-computer.js +0 -463
- package/static/v3/js/views/network.52a4f181.js +0 -97
- package/static/v3/js/views/network.js +0 -97
- package/static/v3/js/views/pipeline.c522f1ce.js +0 -157
- package/static/v3/js/views/pipeline.js +0 -157
- package/static/v3/js/views/planning.4876fd77.js +0 -174
- package/static/v3/js/views/planning.js +0 -174
- package/static/v3/js/views/runs.b63b2afa.js +0 -144
- package/static/v3/js/views/runs.js +0 -144
- package/static/v3/js/views/settings.b7140634.js +0 -317
- package/static/v3/js/views/settings.js +0 -317
- package/static/v3/js/views/skills.c6c2f965.js +0 -109
- package/static/v3/js/views/skills.js +0 -109
- package/static/v3/js/views/snapshots.6f5db095.js +0 -135
- package/static/v3/js/views/snapshots.js +0 -135
- package/static/v3/js/views/tools.e4f11276.js +0 -108
- package/static/v3/js/views/tools.js +0 -108
- package/static/v3/js/views/workflows.7752225a.js +0 -213
- package/static/v3/js/views/workflows.js +0 -213
- package/static/v3/js/views/workspace-admin.c466029b.js +0 -156
- package/static/v3/js/views/workspace-admin.js +0 -156
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
|
|
3
|
+
export type Theme = "dark" | "light";
|
|
4
|
+
export type WorkspaceMode = "basic" | "advanced" | "admin";
|
|
5
|
+
|
|
6
|
+
type AppState = {
|
|
7
|
+
theme: Theme;
|
|
8
|
+
mode: WorkspaceMode;
|
|
9
|
+
workspaceId: string | null;
|
|
10
|
+
apiBase: string | null;
|
|
11
|
+
setTheme: (theme: Theme) => void;
|
|
12
|
+
setMode: (mode: WorkspaceMode) => void;
|
|
13
|
+
setWorkspaceId: (workspaceId: string | null) => void;
|
|
14
|
+
setApiBase: (apiBase: string | null) => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function readTheme(): Theme {
|
|
18
|
+
try {
|
|
19
|
+
const saved = localStorage.getItem("lattice.theme");
|
|
20
|
+
if (saved === "light" || saved === "dark") return saved;
|
|
21
|
+
} catch {}
|
|
22
|
+
return "dark";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readMode(): WorkspaceMode {
|
|
26
|
+
try {
|
|
27
|
+
const saved = localStorage.getItem("lattice.mode");
|
|
28
|
+
if (saved === "basic" || saved === "advanced" || saved === "admin") return saved;
|
|
29
|
+
} catch {}
|
|
30
|
+
return "basic";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const useAppStore = create<AppState>((set) => ({
|
|
34
|
+
theme: readTheme(),
|
|
35
|
+
mode: readMode(),
|
|
36
|
+
workspaceId: null,
|
|
37
|
+
apiBase: null,
|
|
38
|
+
setTheme: (theme) => {
|
|
39
|
+
document.documentElement.dataset.theme = theme;
|
|
40
|
+
try { localStorage.setItem("lattice.theme", theme); } catch {}
|
|
41
|
+
set({ theme });
|
|
42
|
+
},
|
|
43
|
+
setMode: (mode) => {
|
|
44
|
+
try { localStorage.setItem("lattice.mode", mode); } catch {}
|
|
45
|
+
set({ mode });
|
|
46
|
+
},
|
|
47
|
+
setWorkspaceId: (workspaceId) => {
|
|
48
|
+
if (workspaceId) {
|
|
49
|
+
try { localStorage.setItem("lattice.workspace", workspaceId); } catch {}
|
|
50
|
+
}
|
|
51
|
+
set({ workspaceId });
|
|
52
|
+
},
|
|
53
|
+
setApiBase: (apiBase) => set({ apiBase }),
|
|
54
|
+
}));
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "reactflow/dist/style.css";
|
|
3
|
+
|
|
4
|
+
:root {
|
|
5
|
+
color-scheme: dark;
|
|
6
|
+
--background: 220 18% 8%;
|
|
7
|
+
--foreground: 210 30% 96%;
|
|
8
|
+
--card: 220 16% 11%;
|
|
9
|
+
--card-foreground: 210 30% 96%;
|
|
10
|
+
--muted: 218 13% 18%;
|
|
11
|
+
--muted-foreground: 215 14% 66%;
|
|
12
|
+
--primary: 176 68% 48%;
|
|
13
|
+
--primary-foreground: 212 26% 8%;
|
|
14
|
+
--secondary: 221 14% 20%;
|
|
15
|
+
--secondary-foreground: 210 30% 96%;
|
|
16
|
+
--destructive: 355 78% 58%;
|
|
17
|
+
--destructive-foreground: 0 0% 100%;
|
|
18
|
+
--border: 218 13% 23%;
|
|
19
|
+
--input: 218 13% 24%;
|
|
20
|
+
--ring: 176 68% 48%;
|
|
21
|
+
--brain: 176 68% 48%;
|
|
22
|
+
--ask: 216 78% 66%;
|
|
23
|
+
--capture: 142 58% 56%;
|
|
24
|
+
--act: 33 92% 58%;
|
|
25
|
+
--library: 262 68% 70%;
|
|
26
|
+
--system: 350 72% 68%;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
:root[data-theme="light"] {
|
|
30
|
+
color-scheme: light;
|
|
31
|
+
--background: 210 30% 98%;
|
|
32
|
+
--foreground: 222 38% 9%;
|
|
33
|
+
--card: 0 0% 100%;
|
|
34
|
+
--card-foreground: 222 38% 9%;
|
|
35
|
+
--muted: 213 24% 92%;
|
|
36
|
+
--muted-foreground: 219 11% 38%;
|
|
37
|
+
--primary: 178 72% 32%;
|
|
38
|
+
--primary-foreground: 0 0% 100%;
|
|
39
|
+
--secondary: 211 24% 90%;
|
|
40
|
+
--secondary-foreground: 222 38% 9%;
|
|
41
|
+
--destructive: 355 72% 48%;
|
|
42
|
+
--destructive-foreground: 0 0% 100%;
|
|
43
|
+
--border: 214 20% 83%;
|
|
44
|
+
--input: 214 20% 78%;
|
|
45
|
+
--ring: 178 72% 32%;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@theme inline {
|
|
49
|
+
--color-background: hsl(var(--background));
|
|
50
|
+
--color-foreground: hsl(var(--foreground));
|
|
51
|
+
--color-card: hsl(var(--card));
|
|
52
|
+
--color-card-foreground: hsl(var(--card-foreground));
|
|
53
|
+
--color-muted: hsl(var(--muted));
|
|
54
|
+
--color-muted-foreground: hsl(var(--muted-foreground));
|
|
55
|
+
--color-primary: hsl(var(--primary));
|
|
56
|
+
--color-primary-foreground: hsl(var(--primary-foreground));
|
|
57
|
+
--color-secondary: hsl(var(--secondary));
|
|
58
|
+
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
|
59
|
+
--color-destructive: hsl(var(--destructive));
|
|
60
|
+
--color-destructive-foreground: hsl(var(--destructive-foreground));
|
|
61
|
+
--color-border: hsl(var(--border));
|
|
62
|
+
--color-input: hsl(var(--input));
|
|
63
|
+
--color-ring: hsl(var(--ring));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
* {
|
|
67
|
+
box-sizing: border-box;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
body {
|
|
71
|
+
min-width: 320px;
|
|
72
|
+
margin: 0;
|
|
73
|
+
background: hsl(var(--background));
|
|
74
|
+
color: hsl(var(--foreground));
|
|
75
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
button,
|
|
79
|
+
input,
|
|
80
|
+
textarea,
|
|
81
|
+
select {
|
|
82
|
+
font: inherit;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.brain-grid {
|
|
86
|
+
background-image:
|
|
87
|
+
linear-gradient(hsl(var(--border) / 0.34) 1px, transparent 1px),
|
|
88
|
+
linear-gradient(90deg, hsl(var(--border) / 0.34) 1px, transparent 1px);
|
|
89
|
+
background-size: 32px 32px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.scrollbar-thin {
|
|
93
|
+
scrollbar-width: thin;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.react-flow__node {
|
|
97
|
+
border: 1px solid hsl(var(--border));
|
|
98
|
+
border-radius: 8px;
|
|
99
|
+
background: hsl(var(--card));
|
|
100
|
+
color: hsl(var(--foreground));
|
|
101
|
+
padding: 8px 10px;
|
|
102
|
+
font-size: 12px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.react-flow__edge-path {
|
|
106
|
+
stroke: hsl(var(--primary));
|
|
107
|
+
}
|
package/kg_schema.py
CHANGED
package/knowledge_graph.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
"""Compatibility shim for the v4 brain store.
|
|
1
|
+
"""Compatibility shim for the v4.2 lattice-brain store.
|
|
2
2
|
|
|
3
|
-
The implementation now lives under :mod:`
|
|
3
|
+
The implementation now lives under :mod:`lattice_brain`. Root imports are
|
|
4
4
|
kept for older integrations and tests.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from
|
|
7
|
+
from lattice_brain._kg_common import ( # noqa: F401
|
|
8
8
|
EDGE_VERB,
|
|
9
9
|
GRAPH_SCHEMA_VERSION,
|
|
10
10
|
LOCAL_CODE_EXTENSIONS,
|
|
@@ -24,7 +24,7 @@ from latticeai.brain._kg_common import ( # noqa: F401
|
|
|
24
24
|
_slug,
|
|
25
25
|
set_llm_router,
|
|
26
26
|
)
|
|
27
|
-
from
|
|
27
|
+
from lattice_brain.store import KnowledgeGraphStore
|
|
28
28
|
|
|
29
29
|
__all__ = [
|
|
30
30
|
"KnowledgeGraphStore",
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""lattice-brain — independent Brain Core package for Lattice AI.
|
|
2
|
+
|
|
3
|
+
Heavy graph modules are lazy-loaded so storage and archive utilities remain
|
|
4
|
+
usable without importing the FastAPI application or creating runtime globals.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .archive import BrainArchivePaths, EncryptedBrainArchive
|
|
8
|
+
from .core import BrainCore, BrainCoreConfig
|
|
9
|
+
from .storage import (
|
|
10
|
+
DockerPostgresPlan,
|
|
11
|
+
DockerPostgresWizard,
|
|
12
|
+
PostgresConfig,
|
|
13
|
+
PostgresEngine,
|
|
14
|
+
SQLiteEngine,
|
|
15
|
+
SQLiteToPostgresMigrator,
|
|
16
|
+
StorageCapabilities,
|
|
17
|
+
StorageEngine,
|
|
18
|
+
StorageUnavailable,
|
|
19
|
+
storage_from_env,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__version__ = "4.2.0"
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"AssembledContext",
|
|
26
|
+
"BrainArchivePaths",
|
|
27
|
+
"BrainCore",
|
|
28
|
+
"BrainCoreConfig",
|
|
29
|
+
"BrainMemory",
|
|
30
|
+
"ContextAssembler",
|
|
31
|
+
"ContextSection",
|
|
32
|
+
"ConversationStore",
|
|
33
|
+
"DockerPostgresPlan",
|
|
34
|
+
"DockerPostgresWizard",
|
|
35
|
+
"EncryptedBrainArchive",
|
|
36
|
+
"KnowledgeGraphStore",
|
|
37
|
+
"PostgresConfig",
|
|
38
|
+
"PostgresEngine",
|
|
39
|
+
"SQLiteEngine",
|
|
40
|
+
"SQLiteToPostgresMigrator",
|
|
41
|
+
"StorageCapabilities",
|
|
42
|
+
"StorageEngine",
|
|
43
|
+
"StorageUnavailable",
|
|
44
|
+
"storage_from_env",
|
|
45
|
+
"__version__",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def __getattr__(name: str):
|
|
50
|
+
if name in {"AssembledContext", "ContextAssembler", "ContextSection"}:
|
|
51
|
+
from .context import AssembledContext, ContextAssembler, ContextSection
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
"AssembledContext": AssembledContext,
|
|
55
|
+
"ContextAssembler": ContextAssembler,
|
|
56
|
+
"ContextSection": ContextSection,
|
|
57
|
+
}[name]
|
|
58
|
+
if name == "ConversationStore":
|
|
59
|
+
from .conversations import ConversationStore
|
|
60
|
+
|
|
61
|
+
return ConversationStore
|
|
62
|
+
if name == "BrainMemory":
|
|
63
|
+
from .memory import BrainMemory
|
|
64
|
+
|
|
65
|
+
return BrainMemory
|
|
66
|
+
if name == "KnowledgeGraphStore":
|
|
67
|
+
from .store import KnowledgeGraphStore
|
|
68
|
+
|
|
69
|
+
return KnowledgeGraphStore
|
|
70
|
+
raise AttributeError(name)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from latticeai.brain._kg_common import * # noqa: F401,F403
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Encrypted .latticebrain archive support."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
import tempfile
|
|
10
|
+
import zipfile
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Dict, Optional
|
|
15
|
+
|
|
16
|
+
from cryptography.hazmat.primitives import hashes
|
|
17
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
18
|
+
from cryptography.exceptions import InvalidTag
|
|
19
|
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
ARCHIVE_FORMAT = "latticebrain.encrypted"
|
|
23
|
+
ARCHIVE_VERSION = 1
|
|
24
|
+
KDF_ITERATIONS = 390_000
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _now() -> str:
|
|
28
|
+
return datetime.now(timezone.utc).isoformat()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _derive_key(passphrase: str, salt: bytes) -> bytes:
|
|
32
|
+
if not passphrase:
|
|
33
|
+
raise ValueError("A passphrase is required for encrypted .latticebrain archives.")
|
|
34
|
+
kdf = PBKDF2HMAC(
|
|
35
|
+
algorithm=hashes.SHA256(),
|
|
36
|
+
length=32,
|
|
37
|
+
salt=salt,
|
|
38
|
+
iterations=KDF_ITERATIONS,
|
|
39
|
+
)
|
|
40
|
+
return kdf.derive(passphrase.encode("utf-8"))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class BrainArchivePaths:
|
|
45
|
+
db_path: Path
|
|
46
|
+
blob_dir: Optional[Path] = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class EncryptedBrainArchive:
|
|
50
|
+
"""Create and restore encrypted local Brain Core archives."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, paths: BrainArchivePaths) -> None:
|
|
53
|
+
self.paths = paths
|
|
54
|
+
|
|
55
|
+
def create(self, destination: Path, *, passphrase: str) -> Dict[str, object]:
|
|
56
|
+
dest = Path(destination)
|
|
57
|
+
if dest.suffix != ".latticebrain":
|
|
58
|
+
dest = dest.with_suffix(".latticebrain")
|
|
59
|
+
if not self.paths.db_path.exists():
|
|
60
|
+
raise FileNotFoundError(f"Brain database not found: {self.paths.db_path}")
|
|
61
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
62
|
+
with tempfile.TemporaryDirectory() as tmp_s:
|
|
63
|
+
tmp = Path(tmp_s)
|
|
64
|
+
payload = tmp / "payload.zip"
|
|
65
|
+
with zipfile.ZipFile(payload, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
66
|
+
zf.write(self.paths.db_path, "knowledge_graph.sqlite")
|
|
67
|
+
if self.paths.blob_dir and self.paths.blob_dir.exists():
|
|
68
|
+
for file in self.paths.blob_dir.rglob("*"):
|
|
69
|
+
if file.is_file():
|
|
70
|
+
zf.write(file, f"blobs/{file.relative_to(self.paths.blob_dir)}")
|
|
71
|
+
salt = os.urandom(16)
|
|
72
|
+
nonce = os.urandom(12)
|
|
73
|
+
key = _derive_key(passphrase, salt)
|
|
74
|
+
ciphertext = AESGCM(key).encrypt(nonce, payload.read_bytes(), None)
|
|
75
|
+
envelope = {
|
|
76
|
+
"format": ARCHIVE_FORMAT,
|
|
77
|
+
"format_version": ARCHIVE_VERSION,
|
|
78
|
+
"created_at": _now(),
|
|
79
|
+
"kdf": {
|
|
80
|
+
"name": "PBKDF2HMAC-SHA256",
|
|
81
|
+
"iterations": KDF_ITERATIONS,
|
|
82
|
+
"salt": base64.b64encode(salt).decode("ascii"),
|
|
83
|
+
},
|
|
84
|
+
"cipher": {
|
|
85
|
+
"name": "AES-256-GCM",
|
|
86
|
+
"nonce": base64.b64encode(nonce).decode("ascii"),
|
|
87
|
+
},
|
|
88
|
+
"payload": base64.b64encode(ciphertext).decode("ascii"),
|
|
89
|
+
}
|
|
90
|
+
dest.write_text(json.dumps(envelope, indent=2), encoding="utf-8")
|
|
91
|
+
return {"path": str(dest), "bytes": dest.stat().st_size, "encrypted": True}
|
|
92
|
+
|
|
93
|
+
def restore(self, source: Path, *, passphrase: str, target: BrainArchivePaths) -> Dict[str, object]:
|
|
94
|
+
src = Path(source)
|
|
95
|
+
if not src.exists():
|
|
96
|
+
raise FileNotFoundError(f"Brain archive not found: {src}")
|
|
97
|
+
envelope = json.loads(src.read_text(encoding="utf-8"))
|
|
98
|
+
if envelope.get("format") != ARCHIVE_FORMAT:
|
|
99
|
+
raise ValueError("Not a .latticebrain encrypted archive.")
|
|
100
|
+
salt = base64.b64decode(envelope["kdf"]["salt"])
|
|
101
|
+
nonce = base64.b64decode(envelope["cipher"]["nonce"])
|
|
102
|
+
ciphertext = base64.b64decode(envelope["payload"])
|
|
103
|
+
key = _derive_key(passphrase, salt)
|
|
104
|
+
try:
|
|
105
|
+
plaintext = AESGCM(key).decrypt(nonce, ciphertext, None)
|
|
106
|
+
except InvalidTag as exc:
|
|
107
|
+
raise ValueError("Archive decryption failed; passphrase or archive data is invalid.") from exc
|
|
108
|
+
with tempfile.TemporaryDirectory() as tmp_s:
|
|
109
|
+
tmp = Path(tmp_s)
|
|
110
|
+
payload = tmp / "payload.zip"
|
|
111
|
+
payload.write_bytes(plaintext)
|
|
112
|
+
with zipfile.ZipFile(payload) as zf:
|
|
113
|
+
zf.extractall(tmp / "out")
|
|
114
|
+
db_src = tmp / "out" / "knowledge_graph.sqlite"
|
|
115
|
+
if not db_src.exists():
|
|
116
|
+
raise ValueError("Archive payload is missing knowledge_graph.sqlite.")
|
|
117
|
+
target.db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
for sibling in (target.db_path, Path(str(target.db_path) + "-wal"), Path(str(target.db_path) + "-shm")):
|
|
119
|
+
if sibling.exists():
|
|
120
|
+
sibling.unlink()
|
|
121
|
+
shutil.copyfile(db_src, target.db_path)
|
|
122
|
+
blobs_src = tmp / "out" / "blobs"
|
|
123
|
+
if target.blob_dir:
|
|
124
|
+
if target.blob_dir.exists():
|
|
125
|
+
shutil.rmtree(target.blob_dir)
|
|
126
|
+
if blobs_src.exists():
|
|
127
|
+
shutil.copytree(blobs_src, target.blob_dir)
|
|
128
|
+
else:
|
|
129
|
+
target.blob_dir.mkdir(parents=True, exist_ok=True)
|
|
130
|
+
return {"restored": True, "path": str(target.db_path), "encrypted": True}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
__all__ = ["BrainArchivePaths", "EncryptedBrainArchive"]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Independent Brain Core package facade."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
|
|
9
|
+
from .archive import BrainArchivePaths, EncryptedBrainArchive
|
|
10
|
+
from .storage import SQLiteEngine, StorageEngine, StorageUnavailable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class BrainCoreConfig:
|
|
15
|
+
data_dir: Path
|
|
16
|
+
blob_dir: Optional[Path] = None
|
|
17
|
+
storage_engine: Optional[StorageEngine] = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BrainCore:
|
|
21
|
+
"""Stable application boundary for the local Digital Brain.
|
|
22
|
+
|
|
23
|
+
FastAPI, CLI, tests, and future tools should depend on this package-level
|
|
24
|
+
facade instead of constructing scattered storage objects directly.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, config: BrainCoreConfig, *, embedder: Any = None) -> None:
|
|
28
|
+
self.config = config
|
|
29
|
+
self.data_dir = Path(config.data_dir)
|
|
30
|
+
self.db_path = self.data_dir / "knowledge_graph.sqlite"
|
|
31
|
+
self.blob_dir = Path(config.blob_dir) if config.blob_dir else self.data_dir / "knowledge_graph_blobs"
|
|
32
|
+
self.storage_engine = config.storage_engine or SQLiteEngine(self.db_path)
|
|
33
|
+
caps = self.storage_engine.capabilities()
|
|
34
|
+
if not caps.available:
|
|
35
|
+
raise StorageUnavailable(caps.reason or f"{caps.engine} storage is unavailable")
|
|
36
|
+
if caps.engine != "sqlite":
|
|
37
|
+
raise StorageUnavailable(
|
|
38
|
+
"The active FastAPI Brain Core runtime currently requires SQLiteEngine. "
|
|
39
|
+
"Use PostgresEngine through the explicit migration/scale tooling; no SQLite fallback was attempted."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
from .conversations import ConversationStore
|
|
43
|
+
from .store import KnowledgeGraphStore
|
|
44
|
+
|
|
45
|
+
self.knowledge = KnowledgeGraphStore(
|
|
46
|
+
self.db_path,
|
|
47
|
+
self.blob_dir,
|
|
48
|
+
embedder=embedder,
|
|
49
|
+
storage_engine=self.storage_engine,
|
|
50
|
+
)
|
|
51
|
+
self.conversations = ConversationStore(self.db_path)
|
|
52
|
+
self.archive = EncryptedBrainArchive(
|
|
53
|
+
BrainArchivePaths(db_path=self.db_path, blob_dir=self.blob_dir)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def from_paths(
|
|
58
|
+
cls,
|
|
59
|
+
data_dir: Path,
|
|
60
|
+
*,
|
|
61
|
+
blob_dir: Optional[Path] = None,
|
|
62
|
+
embedder: Any = None,
|
|
63
|
+
storage_engine: Optional[StorageEngine] = None,
|
|
64
|
+
) -> "BrainCore":
|
|
65
|
+
return cls(
|
|
66
|
+
BrainCoreConfig(
|
|
67
|
+
data_dir=Path(data_dir),
|
|
68
|
+
blob_dir=blob_dir,
|
|
69
|
+
storage_engine=storage_engine,
|
|
70
|
+
),
|
|
71
|
+
embedder=embedder,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def status(self) -> dict:
|
|
75
|
+
return {
|
|
76
|
+
"storage": self.storage_engine.capabilities().as_dict(),
|
|
77
|
+
"db_path": str(self.db_path),
|
|
78
|
+
"blob_dir": str(self.blob_dir),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
__all__ = ["BrainCore", "BrainCoreConfig"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from latticeai.brain.discovery import * # noqa: F401,F403
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from latticeai.brain.documents import * # noqa: F401,F403
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Local deterministic embeddings used by the standalone Brain Core package."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import math
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import struct
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Iterable, List
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
DEFAULT_EMBEDDING_DIM = int(os.getenv("LATTICEAI_VECTOR_DIM", "384"))
|
|
15
|
+
EMBEDDING_MODEL_ID = f"lattice-local-hash-v1:{DEFAULT_EMBEDDING_DIM}"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _tokenize(text: str) -> List[str]:
|
|
19
|
+
raw = str(text or "").lower()
|
|
20
|
+
tokens = re.findall(r"[a-z0-9][a-z0-9_.:/+-]{1,}|[가-힣]{2,}", raw)
|
|
21
|
+
features: List[str] = []
|
|
22
|
+
for token in tokens:
|
|
23
|
+
features.append(f"tok:{token}")
|
|
24
|
+
if len(token) >= 5 and re.search(r"[a-z]", token):
|
|
25
|
+
for i in range(0, len(token) - 2):
|
|
26
|
+
features.append(f"tri:{token[i:i+3]}")
|
|
27
|
+
if re.search(r"[가-힣]", token) and len(token) >= 3:
|
|
28
|
+
for i in range(0, len(token) - 1):
|
|
29
|
+
features.append(f"ko:{token[i:i+2]}")
|
|
30
|
+
return features
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _hash_to_index(feature: str, dim: int) -> tuple[int, float]:
|
|
34
|
+
digest = hashlib.blake2b(feature.encode("utf-8"), digest_size=8).digest()
|
|
35
|
+
value = int.from_bytes(digest, "big", signed=False)
|
|
36
|
+
sign = 1.0 if (value & 1) == 0 else -1.0
|
|
37
|
+
return value % dim, sign
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class LocalEmbeddingModel:
|
|
42
|
+
"""Deterministic local embedder.
|
|
43
|
+
|
|
44
|
+
This is intentionally not presented as a production semantic model. It is
|
|
45
|
+
a real, offline cosine signal for local-first operation and tests; setup
|
|
46
|
+
wizard provisioning can replace it with a user-consented model/provider.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
dim: int = DEFAULT_EMBEDDING_DIM
|
|
50
|
+
model_id: str = EMBEDDING_MODEL_ID
|
|
51
|
+
|
|
52
|
+
def embed(self, text: str) -> List[float]:
|
|
53
|
+
vector = [0.0] * self.dim
|
|
54
|
+
features = _tokenize(text)
|
|
55
|
+
if not features:
|
|
56
|
+
return vector
|
|
57
|
+
for feature in features:
|
|
58
|
+
index, sign = _hash_to_index(feature, self.dim)
|
|
59
|
+
vector[index] += sign
|
|
60
|
+
norm = math.sqrt(sum(value * value for value in vector))
|
|
61
|
+
if norm <= 0:
|
|
62
|
+
return vector
|
|
63
|
+
return [value / norm for value in vector]
|
|
64
|
+
|
|
65
|
+
def similarity(self, left: Iterable[float], right: Iterable[float]) -> float:
|
|
66
|
+
return float(sum(a * b for a, b in zip(left, right)))
|
|
67
|
+
|
|
68
|
+
def encode(self, vector: Iterable[float]) -> bytes:
|
|
69
|
+
values = list(vector)
|
|
70
|
+
return struct.pack(f"<{len(values)}f", *values)
|
|
71
|
+
|
|
72
|
+
def decode(self, payload: bytes, dim: int | None = None) -> List[float]:
|
|
73
|
+
if not payload:
|
|
74
|
+
return []
|
|
75
|
+
count = int(dim or self.dim)
|
|
76
|
+
expected = count * 4
|
|
77
|
+
if len(payload) != expected:
|
|
78
|
+
count = len(payload) // 4
|
|
79
|
+
return list(struct.unpack(f"<{count}f", payload[: count * 4]))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
__all__ = ["DEFAULT_EMBEDDING_DIM", "EMBEDDING_MODEL_ID", "LocalEmbeddingModel"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from latticeai.brain.ingest import * # noqa: F401,F403
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from latticeai.brain.network import * # noqa: F401,F403
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from latticeai.brain.projection import * # noqa: F401,F403
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from latticeai.brain.provenance import * # noqa: F401,F403
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from latticeai.brain.retrieval import * # noqa: F401,F403
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from latticeai.brain.schema import * # noqa: F401,F403
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Pluggable storage layer for lattice-brain."""
|
|
2
|
+
|
|
3
|
+
from .base import StorageCapabilities, StorageEngine, StorageUnavailable
|
|
4
|
+
from .docker import DockerPostgresPlan, DockerPostgresWizard
|
|
5
|
+
from .factory import storage_from_env
|
|
6
|
+
from .migration import SQLiteToPostgresMigrator, TablePlan
|
|
7
|
+
from .postgres import PostgresConfig, PostgresEngine
|
|
8
|
+
from .sqlite import SQLiteEngine
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"DockerPostgresPlan",
|
|
12
|
+
"DockerPostgresWizard",
|
|
13
|
+
"PostgresConfig",
|
|
14
|
+
"PostgresEngine",
|
|
15
|
+
"SQLiteEngine",
|
|
16
|
+
"SQLiteToPostgresMigrator",
|
|
17
|
+
"StorageCapabilities",
|
|
18
|
+
"StorageEngine",
|
|
19
|
+
"StorageUnavailable",
|
|
20
|
+
"TablePlan",
|
|
21
|
+
"storage_from_env",
|
|
22
|
+
]
|