ltcai 10.0.0 → 10.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/README.md +43 -32
  2. package/docs/CHANGELOG.md +204 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  4. package/docs/DEVELOPMENT.md +1 -1
  5. package/docs/HYBRID_CLOUD_KG_STREAMING.md +101 -0
  6. package/docs/MYPY_BACKLOG.md +98 -0
  7. package/docs/ONBOARDING.md +1 -1
  8. package/docs/OPERATIONS.md +1 -1
  9. package/docs/TRUST_MODEL.md +1 -1
  10. package/docs/WHY_LATTICE.md +1 -1
  11. package/docs/kg-schema.md +1 -1
  12. package/lattice_brain/__init__.py +1 -1
  13. package/lattice_brain/archive.py +5 -4
  14. package/lattice_brain/conversations.py +14 -3
  15. package/lattice_brain/embeddings.py +12 -2
  16. package/lattice_brain/graph/_kg_common.py +5 -5
  17. package/lattice_brain/graph/_kg_fsutil.py +5 -4
  18. package/lattice_brain/graph/discovery.py +4 -3
  19. package/lattice_brain/graph/discovery_index.py +0 -1
  20. package/lattice_brain/graph/documents.py +3 -2
  21. package/lattice_brain/graph/fusion.py +4 -0
  22. package/lattice_brain/graph/ingest.py +12 -2
  23. package/lattice_brain/graph/projection.py +3 -2
  24. package/lattice_brain/graph/provenance.py +5 -3
  25. package/lattice_brain/graph/rerank.py +4 -1
  26. package/lattice_brain/graph/retrieval.py +0 -1
  27. package/lattice_brain/graph/retrieval_docgen.py +0 -1
  28. package/lattice_brain/graph/retrieval_reads.py +0 -1
  29. package/lattice_brain/graph/retrieval_vector.py +0 -1
  30. package/lattice_brain/graph/schema.py +4 -3
  31. package/lattice_brain/graph/store.py +18 -4
  32. package/lattice_brain/graph/write_master.py +46 -1
  33. package/lattice_brain/ingestion.py +4 -1
  34. package/lattice_brain/portability.py +5 -2
  35. package/lattice_brain/quality.py +12 -5
  36. package/lattice_brain/quiet.py +43 -0
  37. package/lattice_brain/runtime/agent_runtime.py +19 -10
  38. package/lattice_brain/runtime/hooks.py +3 -2
  39. package/lattice_brain/runtime/multi_agent.py +2 -3
  40. package/lattice_brain/sensitivity.py +94 -0
  41. package/lattice_brain/storage/base.py +30 -2
  42. package/lattice_brain/storage/migration.py +3 -2
  43. package/lattice_brain/storage/postgres.py +2 -2
  44. package/lattice_brain/storage/sqlite.py +6 -4
  45. package/lattice_brain/workflow.py +4 -2
  46. package/latticeai/__init__.py +1 -1
  47. package/latticeai/api/admin.py +1 -1
  48. package/latticeai/api/agents.py +4 -2
  49. package/latticeai/api/auth.py +5 -1
  50. package/latticeai/api/automation_intelligence.py +2 -1
  51. package/latticeai/api/browser.py +3 -2
  52. package/latticeai/api/chat.py +28 -17
  53. package/latticeai/api/chat_agent_http.py +22 -8
  54. package/latticeai/api/chat_contracts.py +4 -0
  55. package/latticeai/api/chat_documents.py +6 -2
  56. package/latticeai/api/chat_hybrid.py +82 -0
  57. package/latticeai/api/computer_use.py +8 -3
  58. package/latticeai/api/knowledge_graph.py +1 -1
  59. package/latticeai/api/mcp.py +4 -4
  60. package/latticeai/api/models.py +5 -2
  61. package/latticeai/api/network_boundary.py +220 -0
  62. package/latticeai/api/permissions.py +0 -1
  63. package/latticeai/api/realtime.py +1 -1
  64. package/latticeai/api/security_dashboard.py +1 -1
  65. package/latticeai/api/setup.py +16 -3
  66. package/latticeai/api/static_routes.py +2 -1
  67. package/latticeai/api/tools.py +12 -8
  68. package/latticeai/api/voice_capture.py +3 -1
  69. package/latticeai/api/workflow_designer.py +1 -1
  70. package/latticeai/api/workspace.py +1 -2
  71. package/latticeai/app_factory.py +174 -145
  72. package/latticeai/cli/entrypoint.py +6 -4
  73. package/latticeai/core/agent.py +55 -495
  74. package/latticeai/core/agent_eval.py +2 -2
  75. package/latticeai/core/agent_helpers.py +493 -0
  76. package/latticeai/core/agent_prompts.py +0 -1
  77. package/latticeai/core/agent_registry.py +3 -1
  78. package/latticeai/core/agent_state.py +41 -0
  79. package/latticeai/core/audit.py +1 -1
  80. package/latticeai/core/builtin_hooks.py +2 -1
  81. package/latticeai/core/config.py +0 -1
  82. package/latticeai/core/embedding_providers.py +12 -1
  83. package/latticeai/core/file_generation.py +3 -0
  84. package/latticeai/core/invitations.py +4 -1
  85. package/latticeai/core/io_utils.py +3 -1
  86. package/latticeai/core/legacy_compatibility.py +1 -2
  87. package/latticeai/core/local_embeddings.py +12 -1
  88. package/latticeai/core/marketplace.py +1 -2
  89. package/latticeai/core/mcp_registry.py +0 -1
  90. package/latticeai/core/model_compat.py +1 -1
  91. package/latticeai/core/model_resolution.py +1 -1
  92. package/latticeai/core/network_boundary.py +168 -0
  93. package/latticeai/core/oidc.py +3 -0
  94. package/latticeai/core/permission_mode.py +6 -6
  95. package/latticeai/core/plugins.py +3 -2
  96. package/latticeai/core/policy.py +0 -1
  97. package/latticeai/core/quiet.py +84 -0
  98. package/latticeai/core/realtime.py +3 -2
  99. package/latticeai/core/run_store.py +4 -2
  100. package/latticeai/core/security.py +4 -0
  101. package/latticeai/core/users.py +5 -3
  102. package/latticeai/core/workspace_graph_trace.py +3 -0
  103. package/latticeai/core/workspace_os.py +65 -273
  104. package/latticeai/core/workspace_os_constants.py +126 -0
  105. package/latticeai/core/workspace_os_state.py +180 -0
  106. package/latticeai/core/workspace_os_utils.py +3 -1
  107. package/latticeai/core/workspace_permissions.py +1 -0
  108. package/latticeai/core/workspace_snapshots.py +3 -1
  109. package/latticeai/core/workspace_timeline.py +3 -1
  110. package/latticeai/integrations/telegram_bot.py +25 -16
  111. package/latticeai/models/router.py +6 -3
  112. package/latticeai/runtime/access_runtime.py +3 -1
  113. package/latticeai/runtime/audit_runtime.py +3 -2
  114. package/latticeai/runtime/chat_wiring.py +4 -1
  115. package/latticeai/runtime/history_runtime.py +1 -1
  116. package/latticeai/runtime/history_writer.py +138 -0
  117. package/latticeai/runtime/lifespan_runtime.py +3 -1
  118. package/latticeai/runtime/network_boundary_wiring.py +124 -0
  119. package/latticeai/runtime/persistence_runtime.py +3 -1
  120. package/latticeai/runtime/router_registration.py +11 -1
  121. package/latticeai/services/architecture_readiness.py +1 -2
  122. package/latticeai/services/change_proposals.py +2 -1
  123. package/latticeai/services/cloud_egress_audit.py +85 -0
  124. package/latticeai/services/cloud_extraction.py +129 -0
  125. package/latticeai/services/cloud_streaming.py +266 -0
  126. package/latticeai/services/cloud_token_guard.py +84 -0
  127. package/latticeai/services/command_center.py +2 -1
  128. package/latticeai/services/folder_watch.py +3 -0
  129. package/latticeai/services/funnel_metrics.py +3 -1
  130. package/latticeai/services/hybrid_chat.py +265 -0
  131. package/latticeai/services/hybrid_context.py +228 -0
  132. package/latticeai/services/hybrid_policy.py +178 -0
  133. package/latticeai/services/memory_service.py +1 -1
  134. package/latticeai/services/model_catalog.py +10 -1
  135. package/latticeai/services/model_engines.py +35 -14
  136. package/latticeai/services/model_loading.py +3 -2
  137. package/latticeai/services/model_runtime.py +68 -17
  138. package/latticeai/services/multimodal_streaming.py +123 -0
  139. package/latticeai/services/network_boundary_service.py +154 -0
  140. package/latticeai/services/openai_compatible_adapter.py +100 -0
  141. package/latticeai/services/p_reinforce.py +4 -0
  142. package/latticeai/services/platform_runtime.py +9 -4
  143. package/latticeai/services/process_audit.py +0 -1
  144. package/latticeai/services/product_readiness.py +1 -1
  145. package/latticeai/services/run_executor.py +3 -2
  146. package/latticeai/services/search_service.py +1 -4
  147. package/latticeai/services/setup_detection.py +2 -1
  148. package/latticeai/services/tool_dispatch.py +7 -2
  149. package/latticeai/services/upload_service.py +2 -1
  150. package/latticeai/setup/auto_setup.py +10 -7
  151. package/latticeai/setup/wizard.py +15 -11
  152. package/latticeai/tools/__init__.py +3 -3
  153. package/latticeai/tools/commands.py +7 -7
  154. package/latticeai/tools/computer.py +3 -2
  155. package/latticeai/tools/documents.py +10 -9
  156. package/latticeai/tools/filesystem.py +7 -4
  157. package/latticeai/tools/knowledge.py +2 -0
  158. package/latticeai/tools/local_files.py +1 -1
  159. package/latticeai/tools/network.py +2 -1
  160. package/package.json +7 -3
  161. package/scripts/bench_agent_smoke.py +0 -1
  162. package/scripts/bench_models.py +0 -1
  163. package/scripts/brain_quality_eval.py +7 -2
  164. package/scripts/bump_version.py +2 -1
  165. package/scripts/check_current_release_docs.mjs +1 -1
  166. package/scripts/migrate_brain_storage.py +5 -1
  167. package/scripts/profile_kg.py +2 -7
  168. package/scripts/verify_hf_model_registry.py +2 -2
  169. package/src-tauri/Cargo.lock +1 -1
  170. package/src-tauri/Cargo.toml +1 -1
  171. package/src-tauri/tauri.conf.json +1 -1
  172. package/static/app/asset-manifest.json +37 -37
  173. package/static/app/assets/{Act-BtCREeN1.js → Act-DrKIGZeJ.js} +1 -1
  174. package/static/app/assets/{AdminConsole-TPeeN18T.js → AdminConsole-CEV1VefA.js} +1 -1
  175. package/static/app/assets/{Brain-BKs6JAp0.js → Brain-BZSXnwWU.js} +1 -1
  176. package/static/app/assets/{BrainHome-BPGOvSd6.js → BrainHome-CEBd86k6.js} +1 -1
  177. package/static/app/assets/{BrainSignals-CtzQZ15J.js → BrainSignals-O4s6R7av.js} +1 -1
  178. package/static/app/assets/{Capture-1_NaHWqB.js → Capture-AGbOxsg-.js} +1 -1
  179. package/static/app/assets/{CommandPalette-pqvQOXe4.js → CommandPalette-CeqBjCn2.js} +1 -1
  180. package/static/app/assets/Library-CXnbNiQW.js +1 -0
  181. package/static/app/assets/{LivingBrain-DlQ20Q75.js → LivingBrain-D9Iei2ZJ.js} +1 -1
  182. package/static/app/assets/{ProductFlow-BZvGDRi_.js → ProductFlow-JlXiDfMM.js} +1 -1
  183. package/static/app/assets/{ReviewCard-BWgI0D2s.js → ReviewCard-QQly2bTV.js} +1 -1
  184. package/static/app/assets/System-Bp9ZY4ER.js +1 -0
  185. package/static/app/assets/{activity-Dlfk8YC7.js → activity-CxRPOQ2F.js} +1 -1
  186. package/static/app/assets/{bot-CDvUB76P.js → bot-1bcsWB2s.js} +1 -1
  187. package/static/app/assets/{brain-xczrohrt.js → brain-CxaeKCRM.js} +1 -1
  188. package/static/app/assets/{button-SOdH3Oyf.js → button-ZYFqGan7.js} +1 -1
  189. package/static/app/assets/{circle-pause-CG1ythH4.js → circle-pause-B5NOIqvk.js} +1 -1
  190. package/static/app/assets/{circle-play-HXwvjS6W.js → circle-play-BiJVFXf7.js} +1 -1
  191. package/static/app/assets/{cpu-B0d-rGyk.js → cpu-Vvq0OKTr.js} +1 -1
  192. package/static/app/assets/{download-BGIkTQL6.js → download-SEcXzkx1.js} +1 -1
  193. package/static/app/assets/{folder-open-Dst_Z0_K.js → folder-open-Dw1ff_Qa.js} +1 -1
  194. package/static/app/assets/{hard-drive-D53MsWkV.js → hard-drive-B00fR7_r.js} +1 -1
  195. package/static/app/assets/index-CEkL4_M-.css +2 -0
  196. package/static/app/assets/{index-C_IrlQMV.js → index-Dm9VXl1o.js} +3 -3
  197. package/static/app/assets/{input-C5m0riF6.js → input-DtdWYQEa.js} +1 -1
  198. package/static/app/assets/{network-C5a-E5iS.js → network-CpjBu0kI.js} +1 -1
  199. package/static/app/assets/{primitives-vNXYf58F.js → primitives-CwsudBwc.js} +1 -1
  200. package/static/app/assets/search-CwEJWBEA.js +1 -0
  201. package/static/app/assets/{shield-alert-Cc-WVXqN.js → shield-alert-CpjBc-gQ.js} +1 -1
  202. package/static/app/assets/{textarea-BkZ0EqVO.js → textarea-DUPyjHQs.js} +1 -1
  203. package/static/app/assets/{useFocusTrap-CTtKbAOU.js → useFocusTrap-BWcMtCWH.js} +1 -1
  204. package/static/app/assets/{useQuery-Dx1fi4Wu.js → useQuery-Dg5fPqTS.js} +1 -1
  205. package/static/app/assets/{users-BFpQXtEF.js → users-BgcdTQGM.js} +1 -1
  206. package/static/app/assets/{utils-BA_lmW3J.js → utils-a3YXFtEO.js} +2 -2
  207. package/static/app/assets/workspace-BMUEiXUm.js +1 -0
  208. package/static/app/index.html +4 -4
  209. package/static/sw.js +1 -1
  210. package/static/app/assets/Library-DhvoPvC7.js +0 -1
  211. package/static/app/assets/System-CSMdYLMy.js +0 -1
  212. package/static/app/assets/index-FxDusbr0.css +0 -2
  213. package/static/app/assets/search-DhbSgW6m.js +0 -1
  214. package/static/app/assets/workspace-DBPB0jkX.js +0 -1
@@ -19,8 +19,9 @@ import json
19
19
  import logging
20
20
  import sqlite3
21
21
  import threading
22
+ from contextlib import contextmanager
22
23
  from pathlib import Path
23
- from typing import Any, Dict, List, Optional
24
+ from typing import Any, Dict, Iterator, List, Optional
24
25
 
25
26
 
26
27
  def _message_hash(item: Dict[str, Any]) -> str:
@@ -40,11 +41,21 @@ class ConversationStore:
40
41
  self._lock = threading.RLock()
41
42
  self._init_db()
42
43
 
43
- def _connect(self) -> sqlite3.Connection:
44
+ @contextmanager
45
+ def _connect(self) -> Iterator[sqlite3.Connection]:
46
+ """Transactional connection that is closed when the block exits.
47
+
48
+ ``with sqlite3.connect(...)`` commits but never closes; see
49
+ :meth:`lattice_brain.storage.base.StorageEngine.session`.
50
+ """
44
51
  conn = sqlite3.connect(str(self.db_path))
45
52
  conn.row_factory = sqlite3.Row
46
53
  conn.execute("PRAGMA journal_mode=WAL")
47
- return conn
54
+ try:
55
+ with conn:
56
+ yield conn
57
+ finally:
58
+ conn.close()
48
59
 
49
60
  def _init_db(self) -> None:
50
61
  with self._lock, self._connect() as conn:
@@ -10,7 +10,6 @@ import struct
10
10
  from dataclasses import dataclass
11
11
  from typing import Any, Dict, Iterable, List, Optional
12
12
 
13
-
14
13
  DEFAULT_EMBEDDING_DIM = int(os.getenv("LATTICEAI_VECTOR_DIM", "384"))
15
14
  EMBEDDING_MODEL_ID = f"lattice-local-hash-v1:{DEFAULT_EMBEDDING_DIM}"
16
15
 
@@ -71,7 +70,18 @@ class LocalEmbeddingModel:
71
70
  return [value / norm for value in vector]
72
71
 
73
72
  def similarity(self, left: Iterable[float], right: Iterable[float]) -> float:
74
- return float(sum(a * b for a, b in zip(left, right)))
73
+ # strict=True: a dimension mismatch means the two vectors came from
74
+ # different embedding models. Truncating to the shorter one produces a
75
+ # plausible-looking similarity that is meaningless — exactly the silent
76
+ # wrongness this codebase keeps finding. Callers that can hit a model
77
+ # swap already handle failure and fall back to lexical search.
78
+ left_v, right_v = list(left), list(right)
79
+ if len(left_v) != len(right_v):
80
+ raise ValueError(
81
+ f"embedding dimension mismatch: {len(left_v)} vs {len(right_v)}; "
82
+ "the vector index was built with a different model"
83
+ )
84
+ return float(sum(a * b for a, b in zip(left_v, right_v, strict=True)))
75
85
 
76
86
  def encode(self, vector: Iterable[float]) -> bytes:
77
87
  values = list(vector)
@@ -21,12 +21,13 @@ import sqlite3
21
21
  import time
22
22
  import zipfile
23
23
  from collections import Counter
24
+ from contextlib import contextmanager
24
25
  from datetime import datetime
25
26
  from pathlib import Path
26
- from typing import Any, Dict, Iterable, List, Optional, Tuple
27
+ from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple
27
28
 
28
29
  try:
29
- from .schema import KGStoreV2, NodeType, EdgeType, _exec_script
30
+ from .schema import EdgeType, KGStoreV2, NodeType, _exec_script
30
31
  except Exception: # pragma: no cover - v2 schema is optional at import time
31
32
  KGStoreV2 = None # type: ignore[assignment]
32
33
  NodeType = None # type: ignore[assignment]
@@ -44,6 +45,7 @@ _READ_FROM_V2_DEFAULT = os.getenv("LATTICEAI_KG_READ_V2", "1") != "0"
44
45
  # Static constants (projection/format versions, local-ingestion classification
45
46
  # tables, OS exclusion lists) live in ._kg_constants; re-exported here so every
46
47
  # existing ``from ._kg_common import <CONST>`` site is unaffected.
48
+ from ..quiet import quiet
47
49
  from ._kg_constants import ( # noqa: E402
48
50
  _KG_DB_FORMAT_KEY,
49
51
  _KG_DB_FORMAT_VERSION,
@@ -67,7 +69,6 @@ from ._kg_constants import ( # noqa: E402
67
69
  WINDOWS_EXCLUDED_NAMES,
68
70
  )
69
71
 
70
-
71
72
  # Pure fs/path/hash/classification helpers → ._kg_fsutil (re-exported so the
72
73
  # computed __all__ below still forwards them to the graph mixins).
73
74
  from ._kg_fsutil import * # noqa: E402,F401,F403
@@ -152,7 +153,7 @@ def chunk_strategy_for(filename: Any, *, content_type: str = "") -> str:
152
153
  if mime.startswith("text/html") or mime.startswith("text/plain"):
153
154
  return "prose"
154
155
  except Exception:
155
- pass
156
+ quiet()
156
157
  return "plain"
157
158
 
158
159
 
@@ -736,7 +737,6 @@ _CONCEPT_STOP: set = {
736
737
  "yes",
737
738
  "not",
738
739
  "but",
739
- "are",
740
740
  "all",
741
741
  "any",
742
742
  "out",
@@ -11,7 +11,6 @@ them to the graph mixins), so existing call sites are unaffected.
11
11
  from __future__ import annotations
12
12
 
13
13
  # ruff: noqa: F403,F405
14
-
15
14
  import hashlib
16
15
  import math
17
16
  import os
@@ -19,10 +18,12 @@ import platform
19
18
  import re
20
19
  from datetime import datetime
21
20
  from pathlib import Path
22
- from typing import Any, Dict, List, Optional, Tuple
21
+ from typing import Any, Dict, Iterable, List, Optional, Tuple
23
22
 
23
+ from ..quiet import quiet
24
+ from ..utils import now_iso as _now
25
+ from ..utils import parse_iso as _parse_iso
24
26
  from ._kg_constants import * # noqa: F401,F403
25
- from ..utils import now_iso as _now, parse_iso as _parse_iso
26
27
 
27
28
 
28
29
  def _recency_score(
@@ -202,7 +203,7 @@ def _excluded_directory_reason(
202
203
  ):
203
204
  return "user_library"
204
205
  except OSError:
205
- pass
206
+ quiet()
206
207
  if _prefix_blocks(MACOS_EXCLUDED_PREFIXES):
207
208
  return "system_folder"
208
209
  if os_type == "linux":
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- # ruff: noqa: F403,F405
3
+ from ..quiet import quiet
4
4
 
5
+ # ruff: noqa: F403,F405
5
6
  from ._kg_common import * # noqa: F403,F401
6
7
 
7
8
 
@@ -59,7 +60,7 @@ class KnowledgeGraphDiscoveryMixin:
59
60
  ):
60
61
  add(volume.name, volume, "volume", recommended=False)
61
62
  except OSError:
62
- pass
63
+ quiet()
63
64
  elif os_type == "windows":
64
65
  for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
65
66
  drive = Path(f"{letter}:\\")
@@ -87,7 +88,7 @@ class KnowledgeGraphDiscoveryMixin:
87
88
  ):
88
89
  add(mounted.name, mounted, "volume", recommended=False)
89
90
  except OSError:
90
- pass
91
+ quiet()
91
92
 
92
93
  return {
93
94
  "os_type": os_type,
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  # ruff: noqa: F403,F405
4
-
5
4
  from ._kg_common import * # noqa: F403,F401
6
5
 
7
6
 
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- # ruff: noqa: F403,F405
3
+ from ..quiet import quiet
4
4
 
5
+ # ruff: noqa: F403,F405
5
6
  from ._kg_common import * # noqa: F403,F401
6
7
 
7
8
 
@@ -287,7 +288,7 @@ class KnowledgeGraphDocumentsMixin:
287
288
  }
288
289
  )
289
290
  except Exception:
290
- pass
291
+ quiet()
291
292
  result["images"].append(image_info)
292
293
  except Exception as exc:
293
294
  result["error"] = str(exc)
@@ -30,6 +30,8 @@ import os
30
30
  import re
31
31
  from typing import Any, Dict, Mapping, Optional
32
32
 
33
+ from ..quiet import quiet
34
+
33
35
  QUERY_CLASSES = ("fact", "code", "person", "recency")
34
36
 
35
37
  FUSION_WEIGHTS_ENV = "LATTICEAI_FUSION_WEIGHTS"
@@ -118,6 +120,7 @@ def _env_overrides() -> Dict[str, Dict[str, float]]:
118
120
  try:
119
121
  cleaned[key] = max(0.0, min(1.0, float(value)))
120
122
  except (TypeError, ValueError):
123
+ quiet()
121
124
  continue
122
125
  if cleaned:
123
126
  overrides[str(cls)] = cleaned
@@ -137,6 +140,7 @@ def fusion_weight_table(
137
140
  try:
138
141
  table[cls][key] = max(0.0, min(1.0, float(value)))
139
142
  except (TypeError, ValueError):
143
+ quiet()
140
144
  continue
141
145
  return table
142
146
 
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- # ruff: noqa: F403,F405
3
+ from ..quiet import quiet
4
+ from ..sensitivity import stamp_sensitivity
4
5
 
6
+ # ruff: noqa: F403,F405
5
7
  from ._kg_common import * # noqa: F403,F401
6
8
 
7
9
 
@@ -36,7 +38,7 @@ def _triple_edge_metadata(triple: Dict[str, Any]) -> Dict[str, Any]:
36
38
  try:
37
39
  metadata["confidence"] = round(float(confidence), 4)
38
40
  except (TypeError, ValueError):
39
- pass
41
+ quiet()
40
42
  return metadata
41
43
 
42
44
 
@@ -270,6 +272,14 @@ class KnowledgeGraphIngestMixin:
270
272
  "extracted": {k: v for k, v in (extracted or {}).items() if k != "content"},
271
273
  "structure": doc_meta,
272
274
  }
275
+
276
+ # Stamp never-leaves at ingestion. Before 10.2.0 the cloud filter looked
277
+ # for these flags and nothing in the product could set them, so the
278
+ # guard was unreachable. Deriving it from the path means a user who
279
+ # indexes a project folder does not have to remember that it contains a
280
+ # .env — the flag is on the node before any cloud turn can select it.
281
+ stamp_sensitivity(metadata, source_uri or str(path))
282
+
273
283
  full_text = f"{filename}\n{text}"
274
284
  concepts = _extract_concepts(full_text, limit=15)
275
285
  triples = _extract_triples(full_text, concepts)
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- # ruff: noqa: F403,F405
3
+ from ..quiet import quiet
4
4
 
5
+ # ruff: noqa: F403,F405
5
6
  from ._kg_common import * # noqa: F403,F401
6
7
 
7
8
  # ── promotion review mode (review 2026-07-25 Wave 4) ─────────────────────────
@@ -364,7 +365,7 @@ class KnowledgeGraphProjectionMixin:
364
365
  if EdgeType.from_legacy(leg_type).value == leg_type:
365
366
  leg_type = ""
366
367
  except ValueError:
367
- pass
368
+ quiet()
368
369
  norm_type = (
369
370
  EdgeType.from_legacy(edge_type).value if EdgeType is not None else edge_type
370
371
  )
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  # ruff: noqa: F403,F405
4
-
5
4
  from ._kg_common import * # noqa: F403,F401
6
5
 
7
6
 
@@ -188,7 +187,8 @@ class KnowledgeGraphProvenanceMixin:
188
187
  def schema_versions(self) -> Dict[str, Any]:
189
188
  """Versions an exporter stamps and an importer validates against."""
190
189
  try:
191
- from .schema import EMBED_DIM as _EMBED_DIM, KG_SCHEMA_V2_VERSION as _V2
190
+ from .schema import EMBED_DIM as _EMBED_DIM
191
+ from .schema import KG_SCHEMA_V2_VERSION as _V2
192
192
  except Exception: # pragma: no cover - kg_schema always importable in practice
193
193
  _EMBED_DIM, _V2 = 1024, 2
194
194
  return {
@@ -421,7 +421,9 @@ class KnowledgeGraphProvenanceMixin:
421
421
  dest.parent.mkdir(parents=True, exist_ok=True)
422
422
  if dest.exists():
423
423
  dest.unlink() # VACUUM INTO requires the target to not exist
424
- conn = self._connect()
424
+ # Raw connection, not ``_connect()``: VACUUM cannot run inside a
425
+ # transaction, and ``_connect()`` wraps its block in one.
426
+ conn = self.storage_engine.connect()
425
427
  try:
426
428
  conn.execute("PRAGMA wal_checkpoint(FULL)")
427
429
  conn.execute("VACUUM INTO ?", (str(dest),))
@@ -111,7 +111,10 @@ def cross_encoder_rerank(
111
111
  return result
112
112
 
113
113
  ranked = list(candidates)
114
- for item, score in zip(ranked, scores):
114
+ # strict=False on purpose: a cross-encoder that returns fewer scores than
115
+ # candidates leaves the tail un-reranked at its fused score, which is a
116
+ # better outcome than dropping the whole rerank.
117
+ for item, score in zip(ranked, scores, strict=False):
115
118
  item["rerank_score"] = float(score)
116
119
  # Surface the rerank score as the primary ranking key while keeping
117
120
  # the pre-rerank fused score under scores.fused for audit.
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  # ruff: noqa: F403,F405
4
-
5
4
  from ._kg_common import * # noqa: F403,F401
6
5
 
7
6
  # --- Compat seam (v9.9.5 decomposition) -------------------------------------
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  # ruff: noqa: F403,F405
4
-
5
4
  from ._kg_common import * # noqa: F403,F401
6
5
 
7
6
 
@@ -12,7 +12,6 @@ calls (e.g. ``search`` → ``filter_scoped_nodes``) behave exactly as before.
12
12
  from __future__ import annotations
13
13
 
14
14
  # ruff: noqa: F403,F405
15
-
16
15
  from ._kg_common import * # noqa: F403,F401
17
16
 
18
17
 
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  # ruff: noqa: F403,F405
4
-
5
4
  from ._kg_common import * # noqa: F403,F401
6
5
 
7
6
 
@@ -43,13 +43,14 @@ print(store.stats()) # {"nodes": ..., "by_node_type": {...}, ...}
43
43
  from __future__ import annotations
44
44
 
45
45
  import json
46
- import os
47
46
  import logging
47
+ import os
48
48
  import sqlite3
49
49
  from contextlib import contextmanager
50
50
  from enum import Enum
51
51
  from typing import Any, Dict, Optional
52
52
 
53
+ from ..quiet import quiet
53
54
 
54
55
  # ── Schema version ──────────────────────────────────────────────────────────
55
56
  KG_SCHEMA_V2_VERSION = 2
@@ -129,7 +130,7 @@ class NodeType(str, Enum):
129
130
  try:
130
131
  return cls(m.upper())
131
132
  except ValueError:
132
- pass
133
+ quiet()
133
134
  return _LEGACY_NODE_MAP.get(m.lower(), cls.CONCEPT)
134
135
 
135
136
 
@@ -189,7 +190,7 @@ class EdgeType(str, Enum):
189
190
  try:
190
191
  return cls(m.upper())
191
192
  except ValueError:
192
- pass
193
+ quiet()
193
194
  return _LEGACY_EDGE_MAP.get(m.lower(), cls.MENTIONS)
194
195
 
195
196
 
@@ -1,11 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  # ruff: noqa: F403,F405
4
-
5
4
  from ._kg_common import * # noqa: F403,F401
6
- from .documents import KnowledgeGraphDocumentsMixin
7
5
  from .discovery import KnowledgeGraphDiscoveryMixin
8
6
  from .discovery_index import KnowledgeGraphLocalIndexMixin
7
+ from .documents import KnowledgeGraphDocumentsMixin
9
8
  from .ingest import KnowledgeGraphIngestMixin
10
9
  from .projection import KnowledgeGraphProjectionMixin
11
10
  from .provenance import KnowledgeGraphProvenanceMixin
@@ -81,8 +80,23 @@ class KnowledgeGraphStore(
81
80
  return ("kgv2_nodes", "kgv2_edges")
82
81
  return ("nodes", "edges")
83
82
 
84
- def _connect(self) -> sqlite3.Connection:
85
- return self.storage_engine.connect()
83
+ @contextmanager
84
+ def _connect(self) -> Iterator[sqlite3.Connection]:
85
+ """Transactional connection that is closed when the block exits.
86
+
87
+ This used to return the raw connection, so ``with self._connect() as
88
+ conn`` inherited sqlite3's context-manager semantics: commit on success,
89
+ rollback on error, **never close**. Each of the ~58 call sites therefore
90
+ held a descriptor open until garbage collection reclaimed it. That is
91
+ invisible while CPython refcounting frees the local immediately, and
92
+ fatal as soon as anything keeps the frame alive — a coverage tracer, a
93
+ profiler, or a traceback captured by ``logger.exception``.
94
+
95
+ Use ``storage_engine.connect()`` directly only for statements that
96
+ cannot run inside a transaction, and close it yourself.
97
+ """
98
+ with self.storage_engine.session() as conn:
99
+ yield conn
86
100
 
87
101
  def _init_db(self) -> None:
88
102
  with self._connect() as conn:
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  # ruff: noqa: F403,F405
4
-
5
4
  from ._kg_common import * # noqa: F403,F401
6
5
 
7
6
 
@@ -250,3 +249,49 @@ class KnowledgeGraphWriteMixin:
250
249
  text=text,
251
250
  metadata={**metadata, "parent_source_node": source_node},
252
251
  )
252
+
253
+ def set_node_sensitivity(
254
+ self,
255
+ node_id: str,
256
+ *,
257
+ local_only: bool,
258
+ reason: Optional[str] = None,
259
+ ) -> Dict[str, Any]:
260
+ """Mark (or unmark) one node as never-leaving.
261
+
262
+ The cloud filter has always looked for this flag; until 10.2.0 nothing
263
+ could set it, so the guard was unreachable. This is the user-driven
264
+ half — ingestion stamps secret-bearing paths automatically, and this
265
+ covers everything a path cannot tell you, like a note whose *content*
266
+ is private.
267
+
268
+ Unmarking is allowed and audited by the caller: a user who flagged
269
+ something by mistake must be able to undo it, but the reason is cleared
270
+ with the flag so a stale justification cannot linger.
271
+ """
272
+ with self._connect() as conn:
273
+ row = conn.execute(
274
+ "SELECT metadata_json FROM nodes WHERE id=?", (node_id,)
275
+ ).fetchone()
276
+ if row is None:
277
+ return {"ok": False, "node_id": node_id, "reason": "node not found"}
278
+ try:
279
+ metadata = json.loads(row[0] or "{}")
280
+ except Exception:
281
+ metadata = {}
282
+ if local_only:
283
+ metadata["local_only"] = True
284
+ metadata["local_only_reason"] = reason or "marked by the user"
285
+ else:
286
+ metadata.pop("local_only", None)
287
+ metadata.pop("local_only_reason", None)
288
+ conn.execute(
289
+ "UPDATE nodes SET metadata_json=?, updated_at=? WHERE id=?",
290
+ (json.dumps(metadata, ensure_ascii=False), _now(), node_id),
291
+ )
292
+ return {
293
+ "ok": True,
294
+ "node_id": node_id,
295
+ "local_only": bool(local_only),
296
+ "reason": metadata.get("local_only_reason"),
297
+ }
@@ -360,6 +360,9 @@ from .ingestion_jobs import ( # noqa: E402,F401 — re-export for existing impo
360
360
  BackgroundIngestionJob,
361
361
  BackgroundIngestionQueue,
362
362
  )
363
+ from .quiet import ( # noqa: E402 — imported after the module constants it depends on
364
+ quiet, # noqa: E402 — imported after the module constants it depends on
365
+ )
363
366
 
364
367
 
365
368
  @dataclass
@@ -577,7 +580,7 @@ class IngestionPipeline:
577
580
  user_email,
578
581
  )
579
582
  except Exception: # noqa: BLE001 — audit must never break ingestion
580
- pass
583
+ quiet()
581
584
 
582
585
  extraction_quality = self._assess_item_quality(
583
586
  item, source_type=source_type, text=quality_text, chunk_ids=chunk_ids,
@@ -20,10 +20,12 @@ import shutil
20
20
  import sqlite3
21
21
  import tempfile
22
22
  import zipfile
23
+ from contextlib import closing
23
24
  from pathlib import Path, PurePosixPath
24
25
  from typing import Any, Dict, Optional
25
26
 
26
27
  from .archive import BrainArchivePaths, EncryptedBrainArchive
28
+ from .quiet import quiet
27
29
  from .storage import (
28
30
  DockerPostgresWizard,
29
31
  PostgresEngine,
@@ -66,7 +68,7 @@ def _checkpoint_sqlite(db_path: Path) -> None:
66
68
  if not db_path.exists():
67
69
  return
68
70
  try:
69
- with sqlite3.connect(str(db_path)) as conn:
71
+ with closing(sqlite3.connect(str(db_path))) as conn, conn:
70
72
  conn.execute("PRAGMA wal_checkpoint(FULL)")
71
73
  except sqlite3.Error:
72
74
  # Best-effort only. Existing sibling backup/restore still preserves
@@ -99,6 +101,7 @@ def _replace_sqlite_atomically(src: Path, dest: Path, backup_dir: Path) -> None:
99
101
  try:
100
102
  shutil.copy2(sibling, backup)
101
103
  except FileNotFoundError:
104
+ quiet()
102
105
  continue
103
106
  backups[sibling] = backup
104
107
  for sibling in _sqlite_siblings(dest)[1:]:
@@ -247,7 +250,7 @@ class KGPortabilityService:
247
250
  "counts": (artifact.get("header") or {}).get("counts")},
248
251
  )
249
252
  except Exception:
250
- pass
253
+ quiet()
251
254
  return result
252
255
 
253
256
  def import_from_file(self, path, *, mode: str = "merge", dry_run: bool = False) -> Dict[str, Any]:
@@ -7,13 +7,15 @@ Pure Python Quality Layer for Lattice Brain (v6.4+ hardening)
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
+
11
+ import hashlib
10
12
  import math
13
+ import re
11
14
  import time
12
- import hashlib
15
+ from collections import defaultdict
13
16
  from dataclasses import dataclass, field
14
17
  from typing import Any, Dict, List, Optional
15
- from collections import defaultdict
16
- import re
18
+
17
19
 
18
20
  # -----------------------------
19
21
  # 1. Embedding Fallback Labelling + Drift/Reindex Plan
@@ -41,7 +43,12 @@ class EmbeddingFallbackLabeller:
41
43
  conf = 0.3
42
44
  else:
43
45
  # Simple hash-based pseudo-label for determinism
44
- h = hashlib.md5(str(embedding[:4]).encode()).hexdigest()[:8]
46
+ # Not a security hash: a short, stable label for grouping
47
+ # vectors in reports. usedforsecurity=False states that and
48
+ # keeps it working on FIPS builds where md5 is restricted.
49
+ h = hashlib.md5(
50
+ str(embedding[:4]).encode(), usedforsecurity=False
51
+ ).hexdigest()[:8]
45
52
  label = f"emb_cluster_{h}"
46
53
  conf = 0.85
47
54
 
@@ -63,7 +70,7 @@ class EmbeddingFallbackLabeller:
63
70
  def _cosine_distance(self, a: List[float], b: List[float]) -> float:
64
71
  if not a or not b or len(a) != len(b):
65
72
  return 1.0
66
- dot = sum(x*y for x,y in zip(a,b))
73
+ dot = sum(x * y for x, y in zip(a, b, strict=True))
67
74
  na = math.sqrt(sum(x*x for x in a))
68
75
  nb = math.sqrt(sum(x*x for x in b))
69
76
  return 1.0 - (dot / (na*nb + 1e-9))
@@ -0,0 +1,43 @@
1
+ """Deliberately-ignored exceptions, recorded instead of erased (Brain Core).
2
+
3
+ Mirror of :mod:`latticeai.core.quiet`. Brain Core cannot import from the app
4
+ package (``tests/unit/test_import_guard.py``), and the alternative — leaving
5
+ Brain Core's suppressions silent while the app layer's are visible — would make
6
+ the quieter half the harder one to debug.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ import sys
13
+ from typing import Optional
14
+
15
+ logger = logging.getLogger("lattice_brain.suppressed")
16
+
17
+
18
+ def quiet(reason: Optional[str] = None, *, level: int = logging.DEBUG) -> None:
19
+ """Record the exception currently being handled, then continue."""
20
+ exc_type, exc, tb = sys.exc_info()
21
+ if exc is None:
22
+ return
23
+ where = "<unknown>"
24
+ if tb is not None:
25
+ deepest = tb
26
+ while deepest.tb_next is not None:
27
+ deepest = deepest.tb_next
28
+ frame = deepest.tb_frame
29
+ where = f"{frame.f_code.co_filename}:{frame.f_lineno} in {frame.f_code.co_name}"
30
+ label = f" ({reason})" if reason else ""
31
+ if logger.isEnabledFor(level):
32
+ logger.log(
33
+ level,
34
+ "suppressed %s at %s%s: %s",
35
+ getattr(exc_type, "__name__", "Exception"),
36
+ where,
37
+ label,
38
+ exc,
39
+ exc_info=exc,
40
+ )
41
+
42
+
43
+ __all__ = ["quiet"]