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.
Files changed (131) hide show
  1. package/README.md +191 -278
  2. package/docs/CHANGELOG.md +128 -0
  3. package/docs/V4_3_2_DEADCODE_AUDIT_REPORT.md +174 -0
  4. package/docs/V4_3_2_DOCUMENTATION_CLEANUP_REPORT.md +81 -0
  5. package/docs/V4_3_2_GITHUB_VERCEL_CHECK_REPORT.md +75 -0
  6. package/docs/V4_3_2_GRAPH_UX_REPORT.md +48 -0
  7. package/docs/V4_3_2_INDEPENDENT_AUDIT_PACKAGE.md +209 -0
  8. package/docs/V4_3_2_PRODUCT_POLISH_REPORT.md +57 -0
  9. package/docs/V4_3_2_SELF_AUDIT_REPORT.md +63 -0
  10. package/docs/V4_3_2_VALIDATION_REPORT.md +97 -0
  11. package/docs/V4_3_3_VALIDATION_REPORT.md +46 -0
  12. package/docs/V4_4_0_EXTRACTION_REPORT.md +239 -0
  13. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +18 -19
  14. package/frontend/openapi.json +1 -1
  15. package/frontend/src/components/primitives.tsx +92 -10
  16. package/frontend/src/pages/Act.tsx +11 -9
  17. package/frontend/src/pages/Ask.tsx +2 -2
  18. package/frontend/src/pages/Brain.tsx +607 -65
  19. package/frontend/src/pages/Capture.tsx +11 -7
  20. package/frontend/src/pages/Library.tsx +3 -3
  21. package/frontend/src/pages/System.tsx +186 -23
  22. package/lattice_brain/__init__.py +38 -23
  23. package/lattice_brain/_kg_common.py +11 -1
  24. package/lattice_brain/context.py +212 -2
  25. package/lattice_brain/conversations.py +234 -1
  26. package/lattice_brain/discovery.py +11 -1
  27. package/lattice_brain/documents.py +11 -1
  28. package/lattice_brain/graph/__init__.py +28 -0
  29. package/lattice_brain/graph/_kg_common.py +1123 -0
  30. package/lattice_brain/graph/curator.py +473 -0
  31. package/lattice_brain/graph/discovery.py +1455 -0
  32. package/lattice_brain/graph/documents.py +218 -0
  33. package/lattice_brain/graph/identity.py +175 -0
  34. package/lattice_brain/graph/ingest.py +644 -0
  35. package/lattice_brain/graph/network.py +205 -0
  36. package/lattice_brain/graph/projection.py +571 -0
  37. package/lattice_brain/graph/provenance.py +401 -0
  38. package/lattice_brain/graph/retrieval.py +1341 -0
  39. package/lattice_brain/graph/schema.py +640 -0
  40. package/lattice_brain/graph/store.py +237 -0
  41. package/lattice_brain/graph/write_master.py +225 -0
  42. package/lattice_brain/identity.py +11 -13
  43. package/lattice_brain/ingest.py +11 -1
  44. package/lattice_brain/ingestion.py +318 -0
  45. package/lattice_brain/memory.py +100 -1
  46. package/lattice_brain/network.py +11 -1
  47. package/lattice_brain/portability.py +431 -0
  48. package/lattice_brain/projection.py +11 -1
  49. package/lattice_brain/provenance.py +11 -1
  50. package/lattice_brain/retrieval.py +11 -1
  51. package/lattice_brain/runtime/__init__.py +32 -0
  52. package/lattice_brain/runtime/agent_runtime.py +569 -0
  53. package/lattice_brain/runtime/hooks.py +754 -0
  54. package/lattice_brain/runtime/multi_agent.py +795 -0
  55. package/lattice_brain/schema.py +11 -1
  56. package/lattice_brain/store.py +10 -2
  57. package/lattice_brain/workflow.py +461 -0
  58. package/lattice_brain/write_master.py +11 -1
  59. package/latticeai/__init__.py +1 -1
  60. package/latticeai/api/agents.py +2 -2
  61. package/latticeai/api/browser.py +1 -1
  62. package/latticeai/api/chat.py +1 -1
  63. package/latticeai/api/computer_use.py +1 -1
  64. package/latticeai/api/hooks.py +2 -2
  65. package/latticeai/api/mcp.py +1 -1
  66. package/latticeai/api/tools.py +1 -1
  67. package/latticeai/api/workflow_designer.py +2 -2
  68. package/latticeai/app_factory.py +4 -4
  69. package/latticeai/brain/__init__.py +24 -6
  70. package/latticeai/brain/_kg_common.py +11 -1117
  71. package/latticeai/brain/context.py +12 -208
  72. package/latticeai/brain/conversations.py +12 -231
  73. package/latticeai/brain/discovery.py +13 -1451
  74. package/latticeai/brain/documents.py +13 -214
  75. package/latticeai/brain/identity.py +11 -169
  76. package/latticeai/brain/ingest.py +13 -640
  77. package/latticeai/brain/memory.py +12 -97
  78. package/latticeai/brain/network.py +12 -200
  79. package/latticeai/brain/projection.py +13 -567
  80. package/latticeai/brain/provenance.py +13 -397
  81. package/latticeai/brain/retrieval.py +13 -1337
  82. package/latticeai/brain/schema.py +12 -635
  83. package/latticeai/brain/store.py +13 -233
  84. package/latticeai/brain/write_master.py +13 -221
  85. package/latticeai/core/agent.py +1 -1
  86. package/latticeai/core/agent_registry.py +2 -2
  87. package/latticeai/core/builtin_hooks.py +2 -2
  88. package/latticeai/core/graph_curator.py +6 -468
  89. package/latticeai/core/hooks.py +6 -749
  90. package/latticeai/core/marketplace.py +1 -1
  91. package/latticeai/core/multi_agent.py +6 -790
  92. package/latticeai/core/workflow_engine.py +6 -456
  93. package/latticeai/core/workspace_os.py +1 -1
  94. package/latticeai/services/agent_runtime.py +6 -564
  95. package/latticeai/services/ingestion.py +6 -313
  96. package/latticeai/services/kg_portability.py +6 -426
  97. package/latticeai/services/platform_runtime.py +3 -3
  98. package/latticeai/services/run_executor.py +1 -1
  99. package/latticeai/services/upload_service.py +1 -1
  100. package/p_reinforce.py +1 -1
  101. package/package.json +3 -6
  102. package/scripts/build_vercel_static.mjs +77 -0
  103. package/scripts/bump_version.py +1 -1
  104. package/scripts/check_markdown_links.mjs +75 -0
  105. package/scripts/wheel_smoke.py +7 -0
  106. package/src-tauri/Cargo.lock +1 -1
  107. package/src-tauri/Cargo.toml +1 -1
  108. package/src-tauri/src/main.rs +12 -2
  109. package/src-tauri/tauri.conf.json +1 -1
  110. package/static/app/asset-manifest.json +5 -5
  111. package/static/app/assets/index-CHHal8Zl.css +2 -0
  112. package/static/app/assets/index-pdzil9ac.js +333 -0
  113. package/static/app/assets/index-pdzil9ac.js.map +1 -0
  114. package/static/app/index.html +2 -2
  115. package/latticeai/api/deps.py +0 -15
  116. package/scripts/capture/README.md +0 -28
  117. package/scripts/capture/capture_enterprise.js +0 -8
  118. package/scripts/capture/capture_graph.js +0 -8
  119. package/scripts/capture/capture_onboarding.js +0 -8
  120. package/scripts/capture/capture_page.js +0 -43
  121. package/scripts/capture/capture_release_media.js +0 -125
  122. package/scripts/capture/capture_skills.js +0 -8
  123. package/scripts/capture/capture_v340.js +0 -88
  124. package/scripts/capture/capture_workspace.js +0 -8
  125. package/scripts/generate_diagrams.py +0 -512
  126. package/scripts/release-0.3.1.sh +0 -105
  127. package/scripts/take_screenshots.js +0 -69
  128. package/static/app/assets/index-BhPuj8rT.js +0 -333
  129. package/static/app/assets/index-BhPuj8rT.js.map +0 -1
  130. package/static/app/assets/index-yZswHE3d.css +0 -2
  131. package/static/css/tokens.3ba22e37.css +0 -260
@@ -1,218 +1,17 @@
1
- from __future__ import annotations
1
+ """Deprecated shim: physically moved to lattice_brain.graph.documents.
2
2
 
3
- # ruff: noqa: F403,F405
3
+ Kept only for the compatibility window. The module aliases itself to the
4
+ physical module so identity, singletons, and monkeypatching are preserved.
5
+ """
4
6
 
5
- from ._kg_common import * # noqa: F403,F401
7
+ import sys
8
+ import warnings
6
9
 
10
+ import lattice_brain.graph.documents as _impl
7
11
 
8
- class KnowledgeGraphDocumentsMixin:
9
- def _ingest_structure_nodes(
10
- self,
11
- conn: sqlite3.Connection,
12
- file_id: str,
13
- filename: str,
14
- structure: Dict[str, Any],
15
- ) -> None:
16
- for slide in structure.get("slides") or []:
17
- index = slide.get("index")
18
- slide_id = f"slide:{_sha256_text(f'{file_id}:slide:{index}')[:24]}"
19
- title = f"{filename} slide {index}"
20
- summary = "\n".join(slide.get("texts") or [])[:800]
21
- self._upsert_node(
22
- conn, slide_id, "Slide", title, summary=summary, metadata=slide
23
- )
24
- self._upsert_edge(conn, file_id, slide_id, "has_slide")
25
- for text in slide.get("texts") or []:
26
- for topic in _topic_candidates(text, limit=4):
27
- topic_id = f"topic:{_slug(topic)}"
28
- self._upsert_node(
29
- conn,
30
- topic_id,
31
- "Topic",
32
- topic,
33
- metadata={"auto_extracted": True},
34
- )
35
- self._upsert_edge(conn, slide_id, topic_id, "discusses", weight=0.6)
36
-
37
- for page in structure.get("pages") or []:
38
- index = page.get("index")
39
- page_id = f"page:{_sha256_text(f'{file_id}:page:{index}')[:24]}"
40
- title = f"{filename} page {index}"
41
- self._upsert_node(
42
- conn,
43
- page_id,
44
- "Page",
45
- title,
46
- summary=page.get("preview") or "",
47
- metadata=page,
48
- )
49
- self._upsert_edge(conn, file_id, page_id, "has_page")
50
- for topic in _topic_candidates(page.get("preview") or "", limit=4):
51
- topic_id = f"topic:{_slug(topic)}"
52
- self._upsert_node(
53
- conn, topic_id, "Topic", topic, metadata={"auto_extracted": True}
54
- )
55
- self._upsert_edge(conn, page_id, topic_id, "discusses", weight=0.6)
56
-
57
- for sheet in structure.get("sheets") or []:
58
- sheet_title = sheet.get("title")
59
- sheet_id = f"sheet:{_sha256_text(f'{file_id}:sheet:{sheet_title}')[:24]}"
60
- self._upsert_node(
61
- conn, sheet_id, "Sheet", f"{filename} / {sheet_title}", metadata=sheet
62
- )
63
- self._upsert_edge(conn, file_id, sheet_id, "has_sheet")
64
-
65
- for image in structure.get("images") or []:
66
- image_key = image.get("sha256") or _sha256_text(
67
- json.dumps(image, ensure_ascii=False, sort_keys=True)
68
- )
69
- image_id = f"image:{str(image_key)[:24]}"
70
- title_parts = [filename, "image"]
71
- if image.get("page"):
72
- title_parts.append(f"page {image.get('page')}")
73
- if image.get("name"):
74
- title_parts.append(str(image.get("name")).split("/")[-1])
75
- self._upsert_node(
76
- conn, image_id, "Image", " / ".join(title_parts), metadata=image
77
- )
78
- self._upsert_edge(conn, file_id, image_id, "contains_image")
79
-
80
- def _document_structure(self, path: Path, ext: str) -> Dict[str, Any]:
81
- try:
82
- if ext == ".pptx":
83
- return self._pptx_structure(path)
84
- if ext == ".pdf":
85
- return self._pdf_structure(path)
86
- if ext == ".docx":
87
- return self._docx_structure(path)
88
- if ext == ".xlsx":
89
- return self._xlsx_structure(path)
90
- except Exception as exc:
91
- return {"error": str(exc)}
92
- return {}
93
-
94
- def _pptx_structure(self, path: Path) -> Dict[str, Any]:
95
- result: Dict[str, Any] = {"slides": [], "images": []}
96
- try:
97
- from PIL import Image
98
- from pptx import Presentation
99
-
100
- prs = Presentation(str(path))
101
- for slide_index, slide in enumerate(prs.slides, start=1):
102
- slide_info = {"index": slide_index, "shapes": [], "texts": []}
103
- for shape_index, shape in enumerate(slide.shapes, start=1):
104
- shape_info = {
105
- "index": shape_index,
106
- "name": getattr(shape, "name", ""),
107
- "shape_type": str(getattr(shape, "shape_type", "")),
108
- "bbox": {
109
- "left": int(getattr(shape, "left", 0) or 0),
110
- "top": int(getattr(shape, "top", 0) or 0),
111
- "width": int(getattr(shape, "width", 0) or 0),
112
- "height": int(getattr(shape, "height", 0) or 0),
113
- },
114
- }
115
- if getattr(shape, "has_text_frame", False):
116
- text = shape.text_frame.text.strip()
117
- if text:
118
- shape_info["text"] = text[:1000]
119
- slide_info["texts"].append(text)
120
- slide_info["shapes"].append(shape_info)
121
- result["slides"].append(slide_info)
122
- with zipfile.ZipFile(path) as zf:
123
- for name in zf.namelist():
124
- if not name.startswith("ppt/media/"):
125
- continue
126
- data = zf.read(name)
127
- image_info: Dict[str, Any] = {
128
- "name": name,
129
- "bytes": len(data),
130
- "sha256": _sha256_bytes(data),
131
- }
132
- try:
133
- from io import BytesIO
134
-
135
- with Image.open(BytesIO(data)) as img:
136
- image_info.update(
137
- {
138
- "width": img.width,
139
- "height": img.height,
140
- "format": img.format,
141
- }
142
- )
143
- except Exception:
144
- pass
145
- result["images"].append(image_info)
146
- except Exception as exc:
147
- result["error"] = str(exc)
148
- return result
149
-
150
- def _pdf_structure(self, path: Path) -> Dict[str, Any]:
151
- result: Dict[str, Any] = {"pages": [], "images": []}
152
- try:
153
- import pdfplumber
154
-
155
- with pdfplumber.open(str(path)) as pdf:
156
- metadata = dict(pdf.metadata or {})
157
- result["metadata"] = {str(k): str(v) for k, v in metadata.items()}
158
- for page_index, page in enumerate(pdf.pages, start=1):
159
- text = page.extract_text() or ""
160
- page_info = {
161
- "index": page_index,
162
- "width": float(page.width or 0),
163
- "height": float(page.height or 0),
164
- "chars": len(text),
165
- "preview": _clean_text(text)[:500],
166
- "image_count": len(page.images or []),
167
- }
168
- result["pages"].append(page_info)
169
- for image_index, image in enumerate(page.images or [], start=1):
170
- result["images"].append(
171
- {
172
- "page": page_index,
173
- "index": image_index,
174
- "name": image.get("name"),
175
- "width": image.get("width"),
176
- "height": image.get("height"),
177
- "bbox": {
178
- "x0": image.get("x0"),
179
- "top": image.get("top"),
180
- "x1": image.get("x1"),
181
- "bottom": image.get("bottom"),
182
- },
183
- }
184
- )
185
- except Exception as exc:
186
- result["error"] = str(exc)
187
- return result
188
-
189
- def _docx_structure(self, path: Path) -> Dict[str, Any]:
190
- from docx import Document
191
-
192
- doc = Document(str(path))
193
- headings = []
194
- paragraphs = 0
195
- for p in doc.paragraphs:
196
- text = p.text.strip()
197
- if not text:
198
- continue
199
- paragraphs += 1
200
- style = getattr(p.style, "name", "")
201
- if style.lower().startswith("heading"):
202
- headings.append({"style": style, "text": text[:240]})
203
- return {
204
- "paragraphs": paragraphs,
205
- "headings": headings[:80],
206
- "tables": len(doc.tables),
207
- }
208
-
209
- def _xlsx_structure(self, path: Path) -> Dict[str, Any]:
210
- from openpyxl import load_workbook
211
-
212
- wb = load_workbook(str(path), read_only=True, data_only=True)
213
- sheets = []
214
- for ws in wb.worksheets:
215
- sheets.append(
216
- {"title": ws.title, "max_row": ws.max_row, "max_column": ws.max_column}
217
- )
218
- return {"sheets": sheets}
12
+ warnings.warn(
13
+ "latticeai.brain.documents is deprecated; import lattice_brain.graph.documents instead",
14
+ DeprecationWarning,
15
+ stacklevel=2,
16
+ )
17
+ sys.modules[__name__] = _impl
@@ -1,175 +1,17 @@
1
- """Device identity the sovereignty primitive.
1
+ """Deprecated shim: physically moved to lattice_brain.graph.identity.
2
2
 
3
- Every Lattice installation owns an Ed25519 keypair. Exports are signed by
4
- it, peers pair against its public key, and imported knowledge records which
5
- device it came from. The private key never leaves the machine: it lives in
6
- the OS keyring when one is available, otherwise in a 0600 file under the
7
- data directory (the storage backend is reported honestly).
3
+ Kept only for the compatibility window. The module aliases itself to the
4
+ physical module so identity, singletons, and monkeypatching are preserved.
8
5
  """
9
6
 
10
- from __future__ import annotations
7
+ import sys
8
+ import warnings
11
9
 
12
- import base64
13
- import hashlib
14
- import json
15
- import logging
16
- import os
17
- from pathlib import Path
18
- from typing import Any, Dict, Optional
10
+ import lattice_brain.graph.identity as _impl
19
11
 
20
- from cryptography.hazmat.primitives import serialization
21
- from cryptography.hazmat.primitives.asymmetric.ed25519 import (
22
- Ed25519PrivateKey,
23
- Ed25519PublicKey,
12
+ warnings.warn(
13
+ "latticeai.brain.identity is deprecated; import lattice_brain.graph.identity instead",
14
+ DeprecationWarning,
15
+ stacklevel=2,
24
16
  )
25
-
26
- _KEYRING_SERVICE = "lattice-ai-device-identity"
27
- _KEYRING_ENTRY = "ed25519-private-key"
28
-
29
-
30
- def _b64(data: bytes) -> str:
31
- return base64.urlsafe_b64encode(data).decode("ascii").rstrip("=")
32
-
33
-
34
- def _unb64(text: str) -> bytes:
35
- padded = text + "=" * (-len(text) % 4)
36
- return base64.urlsafe_b64decode(padded.encode("ascii"))
37
-
38
-
39
- def _keyring_opt_in() -> bool:
40
- """Keyring storage is opt-in (LATTICEAI_DEVICE_KEY_KEYRING=1).
41
-
42
- OS keychain access can block or prompt during startup/tests; the default
43
- is a 0600 file under the data dir, and ``describe()`` reports which
44
- backend holds the key — no silent security theater either way.
45
- """
46
- return os.getenv("LATTICEAI_DEVICE_KEY_KEYRING", "").strip() in {"1", "true", "yes"}
47
-
48
-
49
- class DeviceIdentity:
50
- """Loads-or-creates the installation's Ed25519 keypair."""
51
-
52
- def __init__(self, data_dir: Path, *, use_keyring: Optional[bool] = None):
53
- if use_keyring is None:
54
- use_keyring = _keyring_opt_in()
55
- self._data_dir = Path(data_dir)
56
- self._key_file = self._data_dir / "device_identity.key"
57
- self._private: Ed25519PrivateKey
58
- self.storage: str # "keyring" | "file"
59
- self._load_or_create(use_keyring)
60
-
61
- # ── key material ───────────────────────────────────────────────────────
62
- def _load_or_create(self, use_keyring: bool) -> None:
63
- raw: Optional[bytes] = None
64
- backend = "file"
65
- if use_keyring:
66
- try:
67
- import keyring
68
-
69
- stored = keyring.get_password(_KEYRING_SERVICE, _KEYRING_ENTRY)
70
- if stored:
71
- raw = _unb64(stored)
72
- backend = "keyring"
73
- except Exception as exc:
74
- logging.debug("device identity: keyring unavailable (%s)", exc)
75
- if raw is None and self._key_file.exists():
76
- raw = _unb64(self._key_file.read_text().strip())
77
- backend = "file"
78
- if raw is None:
79
- key = Ed25519PrivateKey.generate()
80
- raw = key.private_bytes(
81
- serialization.Encoding.Raw,
82
- serialization.PrivateFormat.Raw,
83
- serialization.NoEncryption(),
84
- )
85
- backend = self._persist_new(raw, use_keyring)
86
- self._private = Ed25519PrivateKey.from_private_bytes(raw)
87
- self.storage = backend
88
-
89
- def _persist_new(self, raw: bytes, use_keyring: bool) -> str:
90
- if use_keyring:
91
- try:
92
- import keyring
93
-
94
- keyring.set_password(_KEYRING_SERVICE, _KEYRING_ENTRY, _b64(raw))
95
- return "keyring"
96
- except Exception as exc:
97
- logging.debug("device identity: keyring store failed (%s); using file", exc)
98
- self._data_dir.mkdir(parents=True, exist_ok=True)
99
- self._key_file.write_text(_b64(raw))
100
- os.chmod(self._key_file, 0o600)
101
- return "file"
102
-
103
- # ── public surface ─────────────────────────────────────────────────────
104
- @property
105
- def public_key_b64(self) -> str:
106
- return _b64(
107
- self._private.public_key().public_bytes(
108
- serialization.Encoding.Raw, serialization.PublicFormat.Raw
109
- )
110
- )
111
-
112
- @property
113
- def fingerprint(self) -> str:
114
- """Short human-comparable id: sha256 of the raw public key."""
115
- raw = self._private.public_key().public_bytes(
116
- serialization.Encoding.Raw, serialization.PublicFormat.Raw
117
- )
118
- digest = hashlib.sha256(raw).hexdigest()
119
- return ":".join(digest[i : i + 4] for i in range(0, 16, 4))
120
-
121
- def describe(self) -> Dict[str, Any]:
122
- return {
123
- "fingerprint": self.fingerprint,
124
- "public_key": self.public_key_b64,
125
- "algorithm": "ed25519",
126
- "storage": self.storage,
127
- }
128
-
129
- # ── signing ────────────────────────────────────────────────────────────
130
- def sign(self, payload: bytes) -> str:
131
- return _b64(self._private.sign(payload))
132
-
133
- def sign_manifest(self, manifest: Dict[str, Any]) -> Dict[str, Any]:
134
- """Detached signature over the canonical JSON of a manifest."""
135
- canonical = json.dumps(manifest, sort_keys=True, ensure_ascii=False).encode("utf-8")
136
- return {
137
- "algorithm": "ed25519",
138
- "public_key": self.public_key_b64,
139
- "fingerprint": self.fingerprint,
140
- "signature": self.sign(canonical),
141
- }
142
-
143
-
144
- def fingerprint_of(public_key_b64: str) -> str:
145
- """Human-comparable fingerprint of an Ed25519 public key.
146
-
147
- Raises ValueError when the input is not a valid key — the pairing flow
148
- uses this as its validation gate.
149
- """
150
- raw = _unb64(public_key_b64)
151
- Ed25519PublicKey.from_public_bytes(raw) # validates; raises on garbage
152
- digest = hashlib.sha256(raw).hexdigest()
153
- return ":".join(digest[i : i + 4] for i in range(0, 16, 4))
154
-
155
-
156
- def verify_signature(public_key_b64: str, payload: bytes, signature_b64: str) -> bool:
157
- """True iff ``signature`` is valid for ``payload`` under the given key."""
158
- try:
159
- key = Ed25519PublicKey.from_public_bytes(_unb64(public_key_b64))
160
- key.verify(_unb64(signature_b64), payload)
161
- return True
162
- except Exception:
163
- return False
164
-
165
-
166
- def verify_manifest(manifest: Dict[str, Any], signature_block: Dict[str, Any]) -> bool:
167
- canonical = json.dumps(manifest, sort_keys=True, ensure_ascii=False).encode("utf-8")
168
- return verify_signature(
169
- str(signature_block.get("public_key") or ""),
170
- canonical,
171
- str(signature_block.get("signature") or ""),
172
- )
173
-
174
-
175
- __all__ = ["DeviceIdentity", "verify_signature", "verify_manifest"]
17
+ sys.modules[__name__] = _impl