ltcai 4.3.1 → 4.4.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 +191 -278
- package/docs/CHANGELOG.md +128 -0
- package/docs/V4_3_2_DEADCODE_AUDIT_REPORT.md +174 -0
- package/docs/V4_3_2_DOCUMENTATION_CLEANUP_REPORT.md +81 -0
- package/docs/V4_3_2_GITHUB_VERCEL_CHECK_REPORT.md +75 -0
- package/docs/V4_3_2_GRAPH_UX_REPORT.md +48 -0
- package/docs/V4_3_2_INDEPENDENT_AUDIT_PACKAGE.md +209 -0
- package/docs/V4_3_2_PRODUCT_POLISH_REPORT.md +57 -0
- package/docs/V4_3_2_SELF_AUDIT_REPORT.md +63 -0
- package/docs/V4_3_2_VALIDATION_REPORT.md +97 -0
- package/docs/V4_3_3_VALIDATION_REPORT.md +46 -0
- package/docs/V4_4_0_EXTRACTION_REPORT.md +239 -0
- package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +18 -19
- package/frontend/openapi.json +1 -1
- package/frontend/src/components/primitives.tsx +92 -10
- package/frontend/src/pages/Act.tsx +11 -9
- package/frontend/src/pages/Ask.tsx +2 -2
- package/frontend/src/pages/Brain.tsx +607 -65
- package/frontend/src/pages/Capture.tsx +11 -7
- package/frontend/src/pages/Library.tsx +3 -3
- package/frontend/src/pages/System.tsx +186 -23
- package/lattice_brain/__init__.py +38 -23
- package/lattice_brain/_kg_common.py +11 -1
- package/lattice_brain/context.py +212 -2
- package/lattice_brain/conversations.py +234 -1
- package/lattice_brain/discovery.py +11 -1
- package/lattice_brain/documents.py +11 -1
- package/lattice_brain/graph/__init__.py +28 -0
- package/lattice_brain/graph/_kg_common.py +1123 -0
- package/lattice_brain/graph/curator.py +473 -0
- package/lattice_brain/graph/discovery.py +1455 -0
- package/lattice_brain/graph/documents.py +218 -0
- package/lattice_brain/graph/identity.py +175 -0
- package/lattice_brain/graph/ingest.py +644 -0
- package/lattice_brain/graph/network.py +205 -0
- package/lattice_brain/graph/projection.py +571 -0
- package/lattice_brain/graph/provenance.py +401 -0
- package/lattice_brain/graph/retrieval.py +1341 -0
- package/lattice_brain/graph/schema.py +640 -0
- package/lattice_brain/graph/store.py +237 -0
- package/lattice_brain/graph/write_master.py +225 -0
- package/lattice_brain/identity.py +11 -13
- package/lattice_brain/ingest.py +11 -1
- package/lattice_brain/ingestion.py +318 -0
- package/lattice_brain/memory.py +100 -1
- package/lattice_brain/network.py +11 -1
- package/lattice_brain/portability.py +431 -0
- package/lattice_brain/projection.py +11 -1
- package/lattice_brain/provenance.py +11 -1
- package/lattice_brain/retrieval.py +11 -1
- package/lattice_brain/runtime/__init__.py +32 -0
- package/lattice_brain/runtime/agent_runtime.py +569 -0
- package/lattice_brain/runtime/hooks.py +754 -0
- package/lattice_brain/runtime/multi_agent.py +795 -0
- package/lattice_brain/schema.py +11 -1
- package/lattice_brain/store.py +10 -2
- package/lattice_brain/workflow.py +461 -0
- package/lattice_brain/write_master.py +11 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/agents.py +2 -2
- package/latticeai/api/browser.py +1 -1
- package/latticeai/api/chat.py +1 -1
- package/latticeai/api/computer_use.py +1 -1
- package/latticeai/api/hooks.py +2 -2
- package/latticeai/api/mcp.py +1 -1
- package/latticeai/api/tools.py +1 -1
- package/latticeai/api/workflow_designer.py +2 -2
- package/latticeai/app_factory.py +4 -4
- package/latticeai/brain/__init__.py +24 -6
- package/latticeai/brain/_kg_common.py +11 -1117
- package/latticeai/brain/context.py +12 -208
- package/latticeai/brain/conversations.py +12 -231
- package/latticeai/brain/discovery.py +13 -1451
- package/latticeai/brain/documents.py +13 -214
- package/latticeai/brain/identity.py +11 -169
- package/latticeai/brain/ingest.py +13 -640
- package/latticeai/brain/memory.py +12 -97
- package/latticeai/brain/network.py +12 -200
- package/latticeai/brain/projection.py +13 -567
- package/latticeai/brain/provenance.py +13 -397
- package/latticeai/brain/retrieval.py +13 -1337
- package/latticeai/brain/schema.py +12 -635
- package/latticeai/brain/store.py +13 -233
- package/latticeai/brain/write_master.py +13 -221
- package/latticeai/core/agent.py +1 -1
- package/latticeai/core/agent_registry.py +2 -2
- package/latticeai/core/builtin_hooks.py +2 -2
- package/latticeai/core/graph_curator.py +6 -468
- package/latticeai/core/hooks.py +6 -749
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/multi_agent.py +6 -790
- package/latticeai/core/workflow_engine.py +6 -456
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/services/agent_runtime.py +6 -564
- package/latticeai/services/ingestion.py +6 -313
- package/latticeai/services/kg_portability.py +6 -426
- package/latticeai/services/platform_runtime.py +3 -3
- package/latticeai/services/run_executor.py +1 -1
- package/latticeai/services/upload_service.py +1 -1
- package/p_reinforce.py +1 -1
- package/package.json +3 -6
- package/scripts/build_vercel_static.mjs +77 -0
- package/scripts/bump_version.py +1 -1
- package/scripts/check_markdown_links.mjs +75 -0
- package/scripts/wheel_smoke.py +7 -0
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/src/main.rs +12 -2
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +5 -5
- package/static/app/assets/index-CHHal8Zl.css +2 -0
- package/static/app/assets/index-pdzil9ac.js +333 -0
- package/static/app/assets/index-pdzil9ac.js.map +1 -0
- package/static/app/index.html +2 -2
- package/latticeai/api/deps.py +0 -15
- package/scripts/capture/README.md +0 -28
- package/scripts/capture/capture_enterprise.js +0 -8
- package/scripts/capture/capture_graph.js +0 -8
- package/scripts/capture/capture_onboarding.js +0 -8
- package/scripts/capture/capture_page.js +0 -43
- package/scripts/capture/capture_release_media.js +0 -125
- package/scripts/capture/capture_skills.js +0 -8
- package/scripts/capture/capture_v340.js +0 -88
- package/scripts/capture/capture_workspace.js +0 -8
- package/scripts/generate_diagrams.py +0 -512
- package/scripts/release-0.3.1.sh +0 -105
- package/scripts/take_screenshots.js +0 -69
- package/static/app/assets/index-BhPuj8rT.js +0 -333
- package/static/app/assets/index-BhPuj8rT.js.map +0 -1
- package/static/app/assets/index-yZswHE3d.css +0 -2
- package/static/css/tokens.3ba22e37.css +0 -260
|
@@ -1,431 +1,11 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Compatibility shim: physically moved to ``lattice_brain.portability``.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* **Logical export/import** (JSON): nodes/edges/chunks/sources/provenance with a
|
|
7
|
-
versioned header (schema + projection + embed-dim). Re-embeds on import, so it
|
|
8
|
-
is portable across machines.
|
|
9
|
-
* **Binary backup/restore** (ZIP): a faithful snapshot of the SQLite DB (incl.
|
|
10
|
-
vector embeddings) plus the blob directory, integrity-checked, for
|
|
11
|
-
same-machine recovery.
|
|
3
|
+
Aliases itself to the physical module so identity, module-level state, and
|
|
4
|
+
monkeypatching keep working through the old import path.
|
|
12
5
|
"""
|
|
13
6
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import hashlib
|
|
17
|
-
import json
|
|
18
|
-
import shutil
|
|
19
|
-
import tempfile
|
|
20
|
-
import zipfile
|
|
21
|
-
from datetime import datetime, timezone
|
|
22
|
-
from pathlib import Path, PurePosixPath
|
|
23
|
-
from typing import Any, Dict, Optional
|
|
24
|
-
|
|
25
|
-
from lattice_brain.archive import BrainArchivePaths, EncryptedBrainArchive
|
|
26
|
-
from lattice_brain.storage import (
|
|
27
|
-
DockerPostgresWizard,
|
|
28
|
-
PostgresEngine,
|
|
29
|
-
SQLiteToPostgresMigrator,
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
FORMAT = "latticeai.kg.export"
|
|
33
|
-
FORMAT_VERSION = 1
|
|
34
|
-
BACKUP_FORMAT = "latticeai.kg.backup"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _now_iso() -> str:
|
|
38
|
-
return datetime.now(timezone.utc).isoformat()
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def _stamp() -> str:
|
|
42
|
-
return _now_iso().replace(":", "").replace("-", "").replace(".", "")[:15]
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def _sha256_file(path: Path) -> str:
|
|
46
|
-
h = hashlib.sha256()
|
|
47
|
-
with open(path, "rb") as fh:
|
|
48
|
-
for block in iter(lambda: fh.read(65536), b""):
|
|
49
|
-
h.update(block)
|
|
50
|
-
return h.hexdigest()
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def _safe_zip_names(names) -> None:
|
|
54
|
-
for name in names:
|
|
55
|
-
path = PurePosixPath(name)
|
|
56
|
-
if path.is_absolute() or ".." in path.parts:
|
|
57
|
-
raise ValueError(f"Backup archive contains unsafe path: {name}")
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class KGPortabilityService:
|
|
61
|
-
def __init__(self, *, knowledge_graph: Any, data_dir, enable_graph: bool = True, device_identity: Any = None) -> None:
|
|
62
|
-
self._kg = knowledge_graph
|
|
63
|
-
self._data_dir = Path(data_dir)
|
|
64
|
-
self._enable = bool(enable_graph)
|
|
65
|
-
self._exports_dir = self._data_dir / "workspace_exports"
|
|
66
|
-
# v4 sovereignty: when a DeviceIdentity is wired, exports are signed
|
|
67
|
-
# and imports record origin provenance. Pre-v4 unsigned bundles stay
|
|
68
|
-
# importable locally (origin='unsigned-legacy') — signatures are
|
|
69
|
-
# mandatory only on the Brain Network peer path.
|
|
70
|
-
self._identity = device_identity
|
|
71
|
-
|
|
72
|
-
def available(self) -> bool:
|
|
73
|
-
return self._enable and self._kg is not None
|
|
74
|
-
|
|
75
|
-
def _require(self) -> None:
|
|
76
|
-
if not self.available():
|
|
77
|
-
raise RuntimeError("Knowledge Graph is disabled (LATTICEAI_ENABLE_GRAPH).")
|
|
78
|
-
|
|
79
|
-
# ── logical export / import ──────────────────────────────────────────────
|
|
80
|
-
def export(self, *, workspace_id: Optional[str] = None) -> Dict[str, Any]:
|
|
81
|
-
self._require()
|
|
82
|
-
data = self._kg.export_graph_data(workspace_id=workspace_id)
|
|
83
|
-
header = {
|
|
84
|
-
"format": FORMAT,
|
|
85
|
-
"format_version": FORMAT_VERSION,
|
|
86
|
-
**self._kg.schema_versions(),
|
|
87
|
-
"exported_at": _now_iso(),
|
|
88
|
-
"workspace_id": workspace_id,
|
|
89
|
-
"counts": data.get("counts"),
|
|
90
|
-
}
|
|
91
|
-
artifact = {"header": header, **data}
|
|
92
|
-
if self._identity is not None:
|
|
93
|
-
artifact["signature"] = self._identity.sign_manifest(header)
|
|
94
|
-
return artifact
|
|
95
|
-
|
|
96
|
-
def export_to_file(self, path=None, *, workspace_id: Optional[str] = None) -> Dict[str, Any]:
|
|
97
|
-
artifact = self.export(workspace_id=workspace_id)
|
|
98
|
-
self._exports_dir.mkdir(parents=True, exist_ok=True)
|
|
99
|
-
path = Path(path) if path else self._exports_dir / f"kg-export-{_stamp()}.json"
|
|
100
|
-
path.write_text(json.dumps(artifact, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
101
|
-
return {"path": str(path), "header": artifact["header"], "bytes": path.stat().st_size}
|
|
102
|
-
|
|
103
|
-
def import_data(self, artifact: Dict[str, Any], *, mode: str = "merge", dry_run: bool = False) -> Dict[str, Any]:
|
|
104
|
-
self._require()
|
|
105
|
-
if not isinstance(artifact, dict) or "nodes" not in artifact:
|
|
106
|
-
raise ValueError("Invalid Knowledge Graph export artifact.")
|
|
107
|
-
if mode not in ("merge", "replace"):
|
|
108
|
-
raise ValueError("mode must be 'merge' or 'replace'.")
|
|
109
|
-
origin = "unsigned-legacy"
|
|
110
|
-
signature = artifact.get("signature")
|
|
111
|
-
if signature:
|
|
112
|
-
from lattice_brain.identity import verify_manifest
|
|
113
|
-
|
|
114
|
-
if not verify_manifest(artifact.get("header") or {}, signature):
|
|
115
|
-
raise ValueError("Bundle signature verification failed — refusing to import.")
|
|
116
|
-
origin = f"device:{signature.get('fingerprint') or 'unknown'}"
|
|
117
|
-
result = self._kg.import_graph_data(artifact, mode=mode, dry_run=dry_run)
|
|
118
|
-
result["header"] = artifact.get("header")
|
|
119
|
-
result["origin"] = origin
|
|
120
|
-
result["signed"] = bool(signature)
|
|
121
|
-
if not dry_run:
|
|
122
|
-
try:
|
|
123
|
-
self._kg.record_provenance(
|
|
124
|
-
node_id="import:" + str((artifact.get("header") or {}).get("exported_at") or _now_iso()),
|
|
125
|
-
source_type="bundle_import",
|
|
126
|
-
pipeline="kg-portability",
|
|
127
|
-
owner=None,
|
|
128
|
-
metadata={"origin": origin, "mode": mode,
|
|
129
|
-
"counts": (artifact.get("header") or {}).get("counts")},
|
|
130
|
-
)
|
|
131
|
-
except Exception:
|
|
132
|
-
pass
|
|
133
|
-
return result
|
|
134
|
-
|
|
135
|
-
def import_from_file(self, path, *, mode: str = "merge", dry_run: bool = False) -> Dict[str, Any]:
|
|
136
|
-
artifact = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
137
|
-
return self.import_data(artifact, mode=mode, dry_run=dry_run)
|
|
138
|
-
|
|
139
|
-
# ── binary backup / restore ──────────────────────────────────────────────
|
|
140
|
-
def backup(self, dest_path=None) -> Dict[str, Any]:
|
|
141
|
-
self._require()
|
|
142
|
-
self._exports_dir.mkdir(parents=True, exist_ok=True)
|
|
143
|
-
dest = Path(dest_path) if dest_path else self._exports_dir / f"kg-backup-{_stamp()}.zip"
|
|
144
|
-
with tempfile.TemporaryDirectory() as tmp_s:
|
|
145
|
-
tmp = Path(tmp_s)
|
|
146
|
-
db_copy = tmp / "knowledge_graph.sqlite"
|
|
147
|
-
self._kg.backup_database(db_copy)
|
|
148
|
-
manifest = {
|
|
149
|
-
"format": BACKUP_FORMAT,
|
|
150
|
-
"format_version": FORMAT_VERSION,
|
|
151
|
-
**self._kg.schema_versions(),
|
|
152
|
-
"created_at": _now_iso(),
|
|
153
|
-
"db_sha256": _sha256_file(db_copy),
|
|
154
|
-
"has_blobs": Path(self._kg.blob_dir).exists(),
|
|
155
|
-
}
|
|
156
|
-
with zipfile.ZipFile(dest, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
157
|
-
zf.write(db_copy, "knowledge_graph.sqlite")
|
|
158
|
-
zf.writestr("manifest.json", json.dumps(manifest, ensure_ascii=False, indent=2))
|
|
159
|
-
blob_dir = Path(self._kg.blob_dir)
|
|
160
|
-
if blob_dir.exists():
|
|
161
|
-
for f in blob_dir.rglob("*"):
|
|
162
|
-
if f.is_file():
|
|
163
|
-
zf.write(f, f"blobs/{f.relative_to(blob_dir)}")
|
|
164
|
-
return {"path": str(dest), "bytes": dest.stat().st_size, "manifest": manifest}
|
|
165
|
-
|
|
166
|
-
def restore(
|
|
167
|
-
self,
|
|
168
|
-
archive_path,
|
|
169
|
-
*,
|
|
170
|
-
verify: bool = True,
|
|
171
|
-
dry_run: bool = False,
|
|
172
|
-
confirm: bool = False,
|
|
173
|
-
) -> Dict[str, Any]:
|
|
174
|
-
self._require()
|
|
175
|
-
archive = Path(archive_path)
|
|
176
|
-
if not archive.exists():
|
|
177
|
-
raise FileNotFoundError(f"Backup archive not found: {archive}")
|
|
178
|
-
if not dry_run and not confirm:
|
|
179
|
-
raise ValueError("Explicit confirmation is required before restoring a Knowledge Graph backup.")
|
|
180
|
-
with zipfile.ZipFile(archive) as zf:
|
|
181
|
-
names = zf.namelist()
|
|
182
|
-
_safe_zip_names(names)
|
|
183
|
-
if "knowledge_graph.sqlite" not in names:
|
|
184
|
-
raise ValueError("Archive is missing knowledge_graph.sqlite.")
|
|
185
|
-
manifest = json.loads(zf.read("manifest.json")) if "manifest.json" in names else {}
|
|
186
|
-
with tempfile.TemporaryDirectory() as tmp_s:
|
|
187
|
-
tmp = Path(tmp_s)
|
|
188
|
-
zf.extractall(tmp)
|
|
189
|
-
db_src = tmp / "knowledge_graph.sqlite"
|
|
190
|
-
if verify and manifest.get("db_sha256"):
|
|
191
|
-
if _sha256_file(db_src) != manifest["db_sha256"]:
|
|
192
|
-
raise ValueError("Backup integrity check failed (db sha256 mismatch).")
|
|
193
|
-
if dry_run:
|
|
194
|
-
return {
|
|
195
|
-
"restored": False,
|
|
196
|
-
"dry_run": True,
|
|
197
|
-
"verified": True,
|
|
198
|
-
"manifest": manifest,
|
|
199
|
-
"planned": {
|
|
200
|
-
"database": str(self._kg.db_path),
|
|
201
|
-
"blobs": str(self._kg.blob_dir),
|
|
202
|
-
"archive": str(archive),
|
|
203
|
-
},
|
|
204
|
-
}
|
|
205
|
-
db_dest = Path(self._kg.db_path)
|
|
206
|
-
blob_dest = Path(self._kg.blob_dir)
|
|
207
|
-
db_dest.parent.mkdir(parents=True, exist_ok=True)
|
|
208
|
-
# Drop the live DB + stale WAL/SHM siblings so the restored copy
|
|
209
|
-
# is authoritative (no stale journal overlaying old pages).
|
|
210
|
-
for sib in (db_dest, Path(str(db_dest) + "-wal"), Path(str(db_dest) + "-shm")):
|
|
211
|
-
if sib.exists():
|
|
212
|
-
sib.unlink()
|
|
213
|
-
shutil.copyfile(db_src, db_dest)
|
|
214
|
-
blob_src = tmp / "blobs"
|
|
215
|
-
if blob_src.exists():
|
|
216
|
-
if blob_dest.exists():
|
|
217
|
-
shutil.rmtree(blob_dest)
|
|
218
|
-
shutil.copytree(blob_src, blob_dest)
|
|
219
|
-
else:
|
|
220
|
-
blob_dest.mkdir(parents=True, exist_ok=True)
|
|
221
|
-
stats = self._kg.stats()
|
|
222
|
-
return {
|
|
223
|
-
"restored": True,
|
|
224
|
-
"manifest": manifest,
|
|
225
|
-
"nodes": sum(stats.get("nodes", {}).values()),
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
def verify_backup(self, archive_path) -> Dict[str, Any]:
|
|
229
|
-
archive = Path(archive_path)
|
|
230
|
-
if not archive.exists():
|
|
231
|
-
return {"ok": False, "path": str(archive), "errors": [f"Backup archive not found: {archive}"]}
|
|
232
|
-
try:
|
|
233
|
-
with zipfile.ZipFile(archive) as zf:
|
|
234
|
-
names = zf.namelist()
|
|
235
|
-
_safe_zip_names(names)
|
|
236
|
-
if "knowledge_graph.sqlite" not in names:
|
|
237
|
-
raise ValueError("Archive is missing knowledge_graph.sqlite.")
|
|
238
|
-
manifest = json.loads(zf.read("manifest.json")) if "manifest.json" in names else {}
|
|
239
|
-
with tempfile.TemporaryDirectory() as tmp_s:
|
|
240
|
-
tmp = Path(tmp_s)
|
|
241
|
-
zf.extract("knowledge_graph.sqlite", tmp)
|
|
242
|
-
db_src = tmp / "knowledge_graph.sqlite"
|
|
243
|
-
if manifest.get("db_sha256") and _sha256_file(db_src) != manifest["db_sha256"]:
|
|
244
|
-
raise ValueError("Backup integrity check failed (db sha256 mismatch).")
|
|
245
|
-
return {"ok": True, "path": str(archive), "manifest": manifest, "errors": []}
|
|
246
|
-
except (ValueError, zipfile.BadZipFile, OSError, json.JSONDecodeError) as exc:
|
|
247
|
-
return {"ok": False, "path": str(archive), "errors": [str(exc)]}
|
|
248
|
-
|
|
249
|
-
# ── encrypted .latticebrain archive ───────────────────────────────────
|
|
250
|
-
def encrypted_archive(self, dest_path=None, *, passphrase: str) -> Dict[str, Any]:
|
|
251
|
-
self._require()
|
|
252
|
-
self._exports_dir.mkdir(parents=True, exist_ok=True)
|
|
253
|
-
dest = Path(dest_path) if dest_path else self._exports_dir / f"brain-{_stamp()}.latticebrain"
|
|
254
|
-
metadata = {
|
|
255
|
-
"storage": self.storage_status().get("active", {}),
|
|
256
|
-
"snapshot": self.snapshot_metadata(),
|
|
257
|
-
"device_identity": self._identity.describe() if self._identity is not None else {},
|
|
258
|
-
"provenance": {"exported_at": _now_iso(), "source": "kg-portability"},
|
|
259
|
-
}
|
|
260
|
-
archive = EncryptedBrainArchive(
|
|
261
|
-
BrainArchivePaths(
|
|
262
|
-
db_path=Path(self._kg.db_path),
|
|
263
|
-
blob_dir=Path(self._kg.blob_dir),
|
|
264
|
-
data_dir=self._data_dir,
|
|
265
|
-
metadata=metadata,
|
|
266
|
-
)
|
|
267
|
-
)
|
|
268
|
-
return archive.create(dest, passphrase=passphrase)
|
|
269
|
-
|
|
270
|
-
def inspect_encrypted_archive(self, archive_path, *, passphrase: Optional[str] = None) -> Dict[str, Any]:
|
|
271
|
-
archive = EncryptedBrainArchive(
|
|
272
|
-
BrainArchivePaths(
|
|
273
|
-
db_path=Path(self._kg.db_path),
|
|
274
|
-
blob_dir=Path(self._kg.blob_dir),
|
|
275
|
-
data_dir=self._data_dir,
|
|
276
|
-
)
|
|
277
|
-
)
|
|
278
|
-
return archive.inspect(Path(archive_path), passphrase=passphrase)
|
|
279
|
-
|
|
280
|
-
def verify_encrypted_archive(self, archive_path, *, passphrase: str) -> Dict[str, Any]:
|
|
281
|
-
archive = EncryptedBrainArchive(
|
|
282
|
-
BrainArchivePaths(
|
|
283
|
-
db_path=Path(self._kg.db_path),
|
|
284
|
-
blob_dir=Path(self._kg.blob_dir),
|
|
285
|
-
data_dir=self._data_dir,
|
|
286
|
-
)
|
|
287
|
-
)
|
|
288
|
-
return archive.verify(Path(archive_path), passphrase=passphrase)
|
|
289
|
-
|
|
290
|
-
def restore_encrypted_archive(
|
|
291
|
-
self,
|
|
292
|
-
archive_path,
|
|
293
|
-
*,
|
|
294
|
-
passphrase: str,
|
|
295
|
-
dry_run: bool = False,
|
|
296
|
-
confirm: bool = False,
|
|
297
|
-
) -> Dict[str, Any]:
|
|
298
|
-
self._require()
|
|
299
|
-
archive = EncryptedBrainArchive(
|
|
300
|
-
BrainArchivePaths(
|
|
301
|
-
db_path=Path(self._kg.db_path),
|
|
302
|
-
blob_dir=Path(self._kg.blob_dir),
|
|
303
|
-
data_dir=self._data_dir,
|
|
304
|
-
)
|
|
305
|
-
)
|
|
306
|
-
return archive.restore(
|
|
307
|
-
Path(archive_path),
|
|
308
|
-
passphrase=passphrase,
|
|
309
|
-
target=BrainArchivePaths(
|
|
310
|
-
db_path=Path(self._kg.db_path),
|
|
311
|
-
blob_dir=Path(self._kg.blob_dir),
|
|
312
|
-
data_dir=self._data_dir,
|
|
313
|
-
),
|
|
314
|
-
dry_run=dry_run,
|
|
315
|
-
confirm=confirm,
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
def import_encrypted_archive(
|
|
319
|
-
self,
|
|
320
|
-
archive_path,
|
|
321
|
-
*,
|
|
322
|
-
passphrase: str,
|
|
323
|
-
dry_run: bool = False,
|
|
324
|
-
confirm: bool = False,
|
|
325
|
-
) -> Dict[str, Any]:
|
|
326
|
-
result = self.restore_encrypted_archive(
|
|
327
|
-
archive_path,
|
|
328
|
-
passphrase=passphrase,
|
|
329
|
-
dry_run=dry_run,
|
|
330
|
-
confirm=confirm,
|
|
331
|
-
)
|
|
332
|
-
result["operation"] = "import"
|
|
333
|
-
return result
|
|
334
|
-
|
|
335
|
-
# ── status surface ───────────────────────────────────────────────────────
|
|
336
|
-
def snapshot_metadata(self) -> Dict[str, Any]:
|
|
337
|
-
if not self.available():
|
|
338
|
-
return {"available": False}
|
|
339
|
-
return {
|
|
340
|
-
"available": True,
|
|
341
|
-
**self._kg.schema_versions(),
|
|
342
|
-
"stats": self._kg.stats(),
|
|
343
|
-
"provenance": self._kg.provenance_stats(),
|
|
344
|
-
"storage": (
|
|
345
|
-
self._kg.storage_engine.capabilities().as_dict()
|
|
346
|
-
if getattr(self._kg, "storage_engine", None) is not None
|
|
347
|
-
else {"engine": "sqlite", "available": True}
|
|
348
|
-
),
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
def storage_status(self) -> Dict[str, Any]:
|
|
352
|
-
if not self.available():
|
|
353
|
-
return {"available": False}
|
|
354
|
-
return {
|
|
355
|
-
"available": True,
|
|
356
|
-
"active": (
|
|
357
|
-
self._kg.storage_engine.capabilities().as_dict()
|
|
358
|
-
if getattr(self._kg, "storage_engine", None) is not None
|
|
359
|
-
else {"engine": "sqlite", "available": True}
|
|
360
|
-
),
|
|
361
|
-
"postgres": PostgresEngine("", schema="lattice_brain").capabilities().as_dict(),
|
|
362
|
-
"backup_health": self.backup_health(),
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
def backup_health(self) -> Dict[str, Any]:
|
|
366
|
-
self._exports_dir.mkdir(parents=True, exist_ok=True)
|
|
367
|
-
backups = sorted(
|
|
368
|
-
[
|
|
369
|
-
p for p in self._exports_dir.glob("*")
|
|
370
|
-
if p.is_file() and (p.suffix == ".zip" or p.suffix == ".latticebrain")
|
|
371
|
-
],
|
|
372
|
-
key=lambda p: p.stat().st_mtime,
|
|
373
|
-
reverse=True,
|
|
374
|
-
)
|
|
375
|
-
latest = backups[0] if backups else None
|
|
376
|
-
return {
|
|
377
|
-
"available": True,
|
|
378
|
-
"directory": str(self._exports_dir),
|
|
379
|
-
"count": len(backups),
|
|
380
|
-
"latest": str(latest) if latest else None,
|
|
381
|
-
"latest_bytes": latest.stat().st_size if latest else 0,
|
|
382
|
-
"encrypted_archives": sum(1 for p in backups if p.suffix == ".latticebrain"),
|
|
383
|
-
"zip_backups": sum(1 for p in backups if p.suffix == ".zip"),
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
def postgres_docker_setup(
|
|
387
|
-
self,
|
|
388
|
-
*,
|
|
389
|
-
consent: bool,
|
|
390
|
-
dry_run: bool = False,
|
|
391
|
-
port: int = 5432,
|
|
392
|
-
) -> Dict[str, Any]:
|
|
393
|
-
wizard = DockerPostgresWizard(self._data_dir / "postgres", port=port)
|
|
394
|
-
return wizard.start(consent=consent, dry_run=dry_run)
|
|
7
|
+
import sys
|
|
395
8
|
|
|
396
|
-
|
|
397
|
-
self,
|
|
398
|
-
*,
|
|
399
|
-
dsn: str,
|
|
400
|
-
schema: str = "lattice_brain",
|
|
401
|
-
dry_run: bool = True,
|
|
402
|
-
) -> Dict[str, Any]:
|
|
403
|
-
self._require()
|
|
404
|
-
if not dsn:
|
|
405
|
-
raise ValueError("Postgres DSN is required for SQLite to Postgres migration.")
|
|
406
|
-
migrator = SQLiteToPostgresMigrator(
|
|
407
|
-
Path(self._kg.db_path),
|
|
408
|
-
PostgresEngine(dsn, schema=schema),
|
|
409
|
-
)
|
|
410
|
-
if dry_run:
|
|
411
|
-
return migrator.migrate(dry_run=True)
|
|
412
|
-
backup = self.backup()
|
|
413
|
-
verification = self.verify_backup(backup["path"])
|
|
414
|
-
if not verification.get("ok"):
|
|
415
|
-
raise RuntimeError(
|
|
416
|
-
"Pre-migration backup verification failed; Postgres migration was not started: "
|
|
417
|
-
+ "; ".join(verification.get("errors") or [])
|
|
418
|
-
)
|
|
419
|
-
result = migrator.migrate(dry_run=False)
|
|
420
|
-
result["pre_migration_backup"] = {
|
|
421
|
-
"path": backup["path"],
|
|
422
|
-
"verified": True,
|
|
423
|
-
"manifest": backup.get("manifest"),
|
|
424
|
-
}
|
|
425
|
-
return result
|
|
9
|
+
import lattice_brain.portability as _impl
|
|
426
10
|
|
|
427
|
-
|
|
428
|
-
"""Recent provenance records (newest first) for the ingestion-sources UI."""
|
|
429
|
-
if not self.available():
|
|
430
|
-
return {"items": [], "count": 0}
|
|
431
|
-
return self._kg.list_provenance(limit=limit, source_type=source_type)
|
|
11
|
+
sys.modules[__name__] = _impl
|
|
@@ -17,9 +17,9 @@ from typing import Any, Callable, Dict, Optional, Set
|
|
|
17
17
|
|
|
18
18
|
from fastapi import HTTPException, Request
|
|
19
19
|
|
|
20
|
-
from
|
|
21
|
-
from
|
|
22
|
-
from
|
|
20
|
+
from lattice_brain.runtime.hooks import dispatch_tool
|
|
21
|
+
from lattice_brain.runtime.multi_agent import MultiAgentOrchestrator, default_role_runner, llm_role_runner
|
|
22
|
+
from lattice_brain.workflow import ApprovalRequired, WorkflowEngine
|
|
23
23
|
from tools import execute_tool
|
|
24
24
|
|
|
25
25
|
|
|
@@ -13,7 +13,7 @@ from dataclasses import dataclass
|
|
|
13
13
|
from datetime import datetime
|
|
14
14
|
from typing import Any, Callable, Dict, Optional
|
|
15
15
|
|
|
16
|
-
from
|
|
16
|
+
from lattice_brain.workflow import WorkflowEngine
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
ACTIVE_STATUSES = {"queued", "running", "in_progress", "retrying", "cancelling"}
|
package/p_reinforce.py
CHANGED
|
@@ -118,7 +118,7 @@ class PReinforceGardener:
|
|
|
118
118
|
if self._pipeline is None:
|
|
119
119
|
return {"graph": "unavailable", "graph_detail": "ingestion pipeline not wired"}
|
|
120
120
|
try:
|
|
121
|
-
from
|
|
121
|
+
from lattice_brain.ingestion import IngestionItem
|
|
122
122
|
|
|
123
123
|
ingest = self._pipeline.ingest(
|
|
124
124
|
IngestionItem(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ltcai",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"description": "Lattice AI — local-first Digital Brain Platform (knowledge graph, durable memory, hybrid search, agents, portable encrypted brain archives)",
|
|
5
5
|
"homepage": "https://github.com/TaeSooPark-PTS/LatticeAI#readme",
|
|
6
6
|
"repository": {
|
|
@@ -25,17 +25,14 @@
|
|
|
25
25
|
"check:python": "node scripts/run_python.mjs scripts/check_python.py",
|
|
26
26
|
"lint": "node --check tests/visual/mock_server.cjs && node --check tests/visual/v3.spec.js && npm run lint:frontend",
|
|
27
27
|
"lint:frontend": "node scripts/lint_frontend.mjs",
|
|
28
|
+
"docs:check-links": "node scripts/check_markdown_links.mjs",
|
|
28
29
|
"typecheck": "npm run typecheck:frontend && cd vscode-extension && npm run build",
|
|
29
30
|
"typecheck:frontend": "npx tsc -p tsconfig.json --noEmit",
|
|
30
31
|
"test": "node scripts/run_python.mjs -m pytest tests/ -v",
|
|
31
32
|
"test:unit": "node scripts/run_python.mjs -m pytest tests/unit/ -v",
|
|
32
33
|
"test:integration": "node scripts/run_python.mjs -m pytest tests/integration/ -v",
|
|
33
34
|
"test:visual": "playwright test",
|
|
34
|
-
"
|
|
35
|
-
"capture:graph": "node scripts/capture/capture_graph.js",
|
|
36
|
-
"capture:skills": "node scripts/capture/capture_skills.js",
|
|
37
|
-
"capture:enterprise": "node scripts/capture/capture_enterprise.js",
|
|
38
|
-
"capture:onboarding": "node scripts/capture/capture_onboarding.js",
|
|
35
|
+
"vercel:build": "node scripts/build_vercel_static.mjs",
|
|
39
36
|
"desktop:tauri": "tauri dev",
|
|
40
37
|
"desktop:tauri:build": "tauri build",
|
|
41
38
|
"desktop:tauri:check": "cd src-tauri && cargo check",
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
5
|
+
const outDir = new URL("../vercel-static/", import.meta.url);
|
|
6
|
+
|
|
7
|
+
await mkdir(outDir, { recursive: true });
|
|
8
|
+
|
|
9
|
+
const html = `<!doctype html>
|
|
10
|
+
<html lang="en">
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="utf-8" />
|
|
13
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
14
|
+
<title>Lattice AI ${pkg.version}</title>
|
|
15
|
+
<style>
|
|
16
|
+
:root {
|
|
17
|
+
color-scheme: dark;
|
|
18
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
19
|
+
background: #0f141b;
|
|
20
|
+
color: #e5edf4;
|
|
21
|
+
}
|
|
22
|
+
body {
|
|
23
|
+
margin: 0;
|
|
24
|
+
min-height: 100vh;
|
|
25
|
+
display: grid;
|
|
26
|
+
place-items: center;
|
|
27
|
+
padding: 32px;
|
|
28
|
+
}
|
|
29
|
+
main {
|
|
30
|
+
max-width: 760px;
|
|
31
|
+
border: 1px solid #2d3745;
|
|
32
|
+
border-radius: 8px;
|
|
33
|
+
padding: 32px;
|
|
34
|
+
background: #151b24;
|
|
35
|
+
}
|
|
36
|
+
h1 {
|
|
37
|
+
margin: 0 0 12px;
|
|
38
|
+
font-size: 32px;
|
|
39
|
+
}
|
|
40
|
+
p {
|
|
41
|
+
line-height: 1.6;
|
|
42
|
+
color: #b8c4d2;
|
|
43
|
+
}
|
|
44
|
+
a {
|
|
45
|
+
color: #41ddd2;
|
|
46
|
+
}
|
|
47
|
+
code {
|
|
48
|
+
background: #0f141b;
|
|
49
|
+
border: 1px solid #2d3745;
|
|
50
|
+
border-radius: 4px;
|
|
51
|
+
padding: 2px 6px;
|
|
52
|
+
}
|
|
53
|
+
</style>
|
|
54
|
+
</head>
|
|
55
|
+
<body>
|
|
56
|
+
<main>
|
|
57
|
+
<h1>Lattice AI ${pkg.version}</h1>
|
|
58
|
+
<p>
|
|
59
|
+
Lattice AI is a local-first desktop Digital Brain. The product runtime is the
|
|
60
|
+
Tauri desktop app plus a localhost FastAPI sidecar; it is not hosted on Vercel.
|
|
61
|
+
</p>
|
|
62
|
+
<p>
|
|
63
|
+
This Vercel build is intentionally documentation-only so Git integration checks
|
|
64
|
+
do not try to deploy the desktop runtime or a fake cloud app.
|
|
65
|
+
</p>
|
|
66
|
+
<p>
|
|
67
|
+
Use the validated desktop/package artifacts from the GitHub release process.
|
|
68
|
+
Repository: <a href="${pkg.homepage}">${pkg.homepage}</a>
|
|
69
|
+
</p>
|
|
70
|
+
<p>Runtime route when installed locally: <code>http://127.0.0.1:4825/app</code></p>
|
|
71
|
+
</main>
|
|
72
|
+
</body>
|
|
73
|
+
</html>
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
await writeFile(new URL("index.html", outDir), html, "utf8");
|
|
77
|
+
console.log(`Vercel static placeholder generated for Lattice AI ${pkg.version}`);
|
package/scripts/bump_version.py
CHANGED
|
@@ -26,7 +26,7 @@ TARGETS = [
|
|
|
26
26
|
("lattice_brain/__init__.py", "regex", r'(__version__ = ")([^"]+)(")'),
|
|
27
27
|
("latticeai/core/workspace_os.py", "regex", r'(WORKSPACE_OS_VERSION = ")([^"]+)(")'),
|
|
28
28
|
("latticeai/core/marketplace.py", "regex", r'(MARKETPLACE_VERSION = ")([^"]+)(")'),
|
|
29
|
-
("
|
|
29
|
+
("lattice_brain/runtime/multi_agent.py", "regex", r'(MULTI_AGENT_VERSION = ")([^"]+)(")'),
|
|
30
30
|
("pyproject.toml", "regex", r'(^version = ")([^"]+)(")'),
|
|
31
31
|
("package.json", "json", "version"),
|
|
32
32
|
("package-lock.json", "package-lock", None),
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const root = process.cwd();
|
|
5
|
+
const readme = path.join(root, "README.md");
|
|
6
|
+
const checkedMarkdown = new Set();
|
|
7
|
+
const failures = [];
|
|
8
|
+
|
|
9
|
+
function stripAnchor(target) {
|
|
10
|
+
const hash = target.indexOf("#");
|
|
11
|
+
return hash >= 0 ? target.slice(0, hash) : target;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isExternal(target) {
|
|
15
|
+
return /^(https?:|mailto:|tel:)/i.test(target);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function decodeTarget(target) {
|
|
19
|
+
return decodeURIComponent(target.replace(/^<|>$/g, ""));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function localPath(fromFile, target) {
|
|
23
|
+
const cleaned = stripAnchor(target).trim();
|
|
24
|
+
if (!cleaned || isExternal(cleaned)) return null;
|
|
25
|
+
return path.resolve(path.dirname(fromFile), decodeTarget(cleaned));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function links(markdown) {
|
|
29
|
+
const out = [];
|
|
30
|
+
const linkPattern = /!?\[[^\]]*\]\(([^)]+)\)/g;
|
|
31
|
+
let match;
|
|
32
|
+
while ((match = linkPattern.exec(markdown))) {
|
|
33
|
+
out.push(match[1].trim());
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function checkFileLink(fromFile, target) {
|
|
39
|
+
const resolved = localPath(fromFile, target);
|
|
40
|
+
if (!resolved) return;
|
|
41
|
+
if (!resolved.startsWith(root)) {
|
|
42
|
+
failures.push(`${path.relative(root, fromFile)} links outside repo: ${target}`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (!existsSync(resolved)) {
|
|
46
|
+
failures.push(`${path.relative(root, fromFile)} has missing link: ${target}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function checkMarkdownFile(file) {
|
|
51
|
+
if (checkedMarkdown.has(file)) return;
|
|
52
|
+
checkedMarkdown.add(file);
|
|
53
|
+
const markdown = readFileSync(file, "utf8");
|
|
54
|
+
for (const target of links(markdown)) {
|
|
55
|
+
checkFileLink(file, target);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
checkMarkdownFile(readme);
|
|
60
|
+
|
|
61
|
+
for (const target of links(readFileSync(readme, "utf8"))) {
|
|
62
|
+
const resolved = localPath(readme, target);
|
|
63
|
+
if (!resolved || !existsSync(resolved)) continue;
|
|
64
|
+
if (statSync(resolved).isFile() && resolved.endsWith(".md")) {
|
|
65
|
+
checkMarkdownFile(resolved);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (failures.length) {
|
|
70
|
+
console.error("Markdown link check failed:");
|
|
71
|
+
for (const failure of failures) console.error(`- ${failure}`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(`Markdown link check passed for README and ${checkedMarkdown.size - 1} README-linked Markdown files.`);
|