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.
- package/README.md +43 -32
- package/docs/CHANGELOG.md +204 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/HYBRID_CLOUD_KG_STREAMING.md +101 -0
- package/docs/MYPY_BACKLOG.md +98 -0
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +1 -1
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/kg-schema.md +1 -1
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/archive.py +5 -4
- package/lattice_brain/conversations.py +14 -3
- package/lattice_brain/embeddings.py +12 -2
- package/lattice_brain/graph/_kg_common.py +5 -5
- package/lattice_brain/graph/_kg_fsutil.py +5 -4
- package/lattice_brain/graph/discovery.py +4 -3
- package/lattice_brain/graph/discovery_index.py +0 -1
- package/lattice_brain/graph/documents.py +3 -2
- package/lattice_brain/graph/fusion.py +4 -0
- package/lattice_brain/graph/ingest.py +12 -2
- package/lattice_brain/graph/projection.py +3 -2
- package/lattice_brain/graph/provenance.py +5 -3
- package/lattice_brain/graph/rerank.py +4 -1
- package/lattice_brain/graph/retrieval.py +0 -1
- package/lattice_brain/graph/retrieval_docgen.py +0 -1
- package/lattice_brain/graph/retrieval_reads.py +0 -1
- package/lattice_brain/graph/retrieval_vector.py +0 -1
- package/lattice_brain/graph/schema.py +4 -3
- package/lattice_brain/graph/store.py +18 -4
- package/lattice_brain/graph/write_master.py +46 -1
- package/lattice_brain/ingestion.py +4 -1
- package/lattice_brain/portability.py +5 -2
- package/lattice_brain/quality.py +12 -5
- package/lattice_brain/quiet.py +43 -0
- package/lattice_brain/runtime/agent_runtime.py +19 -10
- package/lattice_brain/runtime/hooks.py +3 -2
- package/lattice_brain/runtime/multi_agent.py +2 -3
- package/lattice_brain/sensitivity.py +94 -0
- package/lattice_brain/storage/base.py +30 -2
- package/lattice_brain/storage/migration.py +3 -2
- package/lattice_brain/storage/postgres.py +2 -2
- package/lattice_brain/storage/sqlite.py +6 -4
- package/lattice_brain/workflow.py +4 -2
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/admin.py +1 -1
- package/latticeai/api/agents.py +4 -2
- package/latticeai/api/auth.py +5 -1
- package/latticeai/api/automation_intelligence.py +2 -1
- package/latticeai/api/browser.py +3 -2
- package/latticeai/api/chat.py +28 -17
- package/latticeai/api/chat_agent_http.py +22 -8
- package/latticeai/api/chat_contracts.py +4 -0
- package/latticeai/api/chat_documents.py +6 -2
- package/latticeai/api/chat_hybrid.py +82 -0
- package/latticeai/api/computer_use.py +8 -3
- package/latticeai/api/knowledge_graph.py +1 -1
- package/latticeai/api/mcp.py +4 -4
- package/latticeai/api/models.py +5 -2
- package/latticeai/api/network_boundary.py +220 -0
- package/latticeai/api/permissions.py +0 -1
- package/latticeai/api/realtime.py +1 -1
- package/latticeai/api/security_dashboard.py +1 -1
- package/latticeai/api/setup.py +16 -3
- package/latticeai/api/static_routes.py +2 -1
- package/latticeai/api/tools.py +12 -8
- package/latticeai/api/voice_capture.py +3 -1
- package/latticeai/api/workflow_designer.py +1 -1
- package/latticeai/api/workspace.py +1 -2
- package/latticeai/app_factory.py +174 -145
- package/latticeai/cli/entrypoint.py +6 -4
- package/latticeai/core/agent.py +55 -495
- package/latticeai/core/agent_eval.py +2 -2
- package/latticeai/core/agent_helpers.py +493 -0
- package/latticeai/core/agent_prompts.py +0 -1
- package/latticeai/core/agent_registry.py +3 -1
- package/latticeai/core/agent_state.py +41 -0
- package/latticeai/core/audit.py +1 -1
- package/latticeai/core/builtin_hooks.py +2 -1
- package/latticeai/core/config.py +0 -1
- package/latticeai/core/embedding_providers.py +12 -1
- package/latticeai/core/file_generation.py +3 -0
- package/latticeai/core/invitations.py +4 -1
- package/latticeai/core/io_utils.py +3 -1
- package/latticeai/core/legacy_compatibility.py +1 -2
- package/latticeai/core/local_embeddings.py +12 -1
- package/latticeai/core/marketplace.py +1 -2
- package/latticeai/core/mcp_registry.py +0 -1
- package/latticeai/core/model_compat.py +1 -1
- package/latticeai/core/model_resolution.py +1 -1
- package/latticeai/core/network_boundary.py +168 -0
- package/latticeai/core/oidc.py +3 -0
- package/latticeai/core/permission_mode.py +6 -6
- package/latticeai/core/plugins.py +3 -2
- package/latticeai/core/policy.py +0 -1
- package/latticeai/core/quiet.py +84 -0
- package/latticeai/core/realtime.py +3 -2
- package/latticeai/core/run_store.py +4 -2
- package/latticeai/core/security.py +4 -0
- package/latticeai/core/users.py +5 -3
- package/latticeai/core/workspace_graph_trace.py +3 -0
- package/latticeai/core/workspace_os.py +65 -273
- package/latticeai/core/workspace_os_constants.py +126 -0
- package/latticeai/core/workspace_os_state.py +180 -0
- package/latticeai/core/workspace_os_utils.py +3 -1
- package/latticeai/core/workspace_permissions.py +1 -0
- package/latticeai/core/workspace_snapshots.py +3 -1
- package/latticeai/core/workspace_timeline.py +3 -1
- package/latticeai/integrations/telegram_bot.py +25 -16
- package/latticeai/models/router.py +6 -3
- package/latticeai/runtime/access_runtime.py +3 -1
- package/latticeai/runtime/audit_runtime.py +3 -2
- package/latticeai/runtime/chat_wiring.py +4 -1
- package/latticeai/runtime/history_runtime.py +1 -1
- package/latticeai/runtime/history_writer.py +138 -0
- package/latticeai/runtime/lifespan_runtime.py +3 -1
- package/latticeai/runtime/network_boundary_wiring.py +124 -0
- package/latticeai/runtime/persistence_runtime.py +3 -1
- package/latticeai/runtime/router_registration.py +11 -1
- package/latticeai/services/architecture_readiness.py +1 -2
- package/latticeai/services/change_proposals.py +2 -1
- package/latticeai/services/cloud_egress_audit.py +85 -0
- package/latticeai/services/cloud_extraction.py +129 -0
- package/latticeai/services/cloud_streaming.py +266 -0
- package/latticeai/services/cloud_token_guard.py +84 -0
- package/latticeai/services/command_center.py +2 -1
- package/latticeai/services/folder_watch.py +3 -0
- package/latticeai/services/funnel_metrics.py +3 -1
- package/latticeai/services/hybrid_chat.py +265 -0
- package/latticeai/services/hybrid_context.py +228 -0
- package/latticeai/services/hybrid_policy.py +178 -0
- package/latticeai/services/memory_service.py +1 -1
- package/latticeai/services/model_catalog.py +10 -1
- package/latticeai/services/model_engines.py +35 -14
- package/latticeai/services/model_loading.py +3 -2
- package/latticeai/services/model_runtime.py +68 -17
- package/latticeai/services/multimodal_streaming.py +123 -0
- package/latticeai/services/network_boundary_service.py +154 -0
- package/latticeai/services/openai_compatible_adapter.py +100 -0
- package/latticeai/services/p_reinforce.py +4 -0
- package/latticeai/services/platform_runtime.py +9 -4
- package/latticeai/services/process_audit.py +0 -1
- package/latticeai/services/product_readiness.py +1 -1
- package/latticeai/services/run_executor.py +3 -2
- package/latticeai/services/search_service.py +1 -4
- package/latticeai/services/setup_detection.py +2 -1
- package/latticeai/services/tool_dispatch.py +7 -2
- package/latticeai/services/upload_service.py +2 -1
- package/latticeai/setup/auto_setup.py +10 -7
- package/latticeai/setup/wizard.py +15 -11
- package/latticeai/tools/__init__.py +3 -3
- package/latticeai/tools/commands.py +7 -7
- package/latticeai/tools/computer.py +3 -2
- package/latticeai/tools/documents.py +10 -9
- package/latticeai/tools/filesystem.py +7 -4
- package/latticeai/tools/knowledge.py +2 -0
- package/latticeai/tools/local_files.py +1 -1
- package/latticeai/tools/network.py +2 -1
- package/package.json +7 -3
- package/scripts/bench_agent_smoke.py +0 -1
- package/scripts/bench_models.py +0 -1
- package/scripts/brain_quality_eval.py +7 -2
- package/scripts/bump_version.py +2 -1
- package/scripts/check_current_release_docs.mjs +1 -1
- package/scripts/migrate_brain_storage.py +5 -1
- package/scripts/profile_kg.py +2 -7
- package/scripts/verify_hf_model_registry.py +2 -2
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +37 -37
- package/static/app/assets/{Act-BtCREeN1.js → Act-DrKIGZeJ.js} +1 -1
- package/static/app/assets/{AdminConsole-TPeeN18T.js → AdminConsole-CEV1VefA.js} +1 -1
- package/static/app/assets/{Brain-BKs6JAp0.js → Brain-BZSXnwWU.js} +1 -1
- package/static/app/assets/{BrainHome-BPGOvSd6.js → BrainHome-CEBd86k6.js} +1 -1
- package/static/app/assets/{BrainSignals-CtzQZ15J.js → BrainSignals-O4s6R7av.js} +1 -1
- package/static/app/assets/{Capture-1_NaHWqB.js → Capture-AGbOxsg-.js} +1 -1
- package/static/app/assets/{CommandPalette-pqvQOXe4.js → CommandPalette-CeqBjCn2.js} +1 -1
- package/static/app/assets/Library-CXnbNiQW.js +1 -0
- package/static/app/assets/{LivingBrain-DlQ20Q75.js → LivingBrain-D9Iei2ZJ.js} +1 -1
- package/static/app/assets/{ProductFlow-BZvGDRi_.js → ProductFlow-JlXiDfMM.js} +1 -1
- package/static/app/assets/{ReviewCard-BWgI0D2s.js → ReviewCard-QQly2bTV.js} +1 -1
- package/static/app/assets/System-Bp9ZY4ER.js +1 -0
- package/static/app/assets/{activity-Dlfk8YC7.js → activity-CxRPOQ2F.js} +1 -1
- package/static/app/assets/{bot-CDvUB76P.js → bot-1bcsWB2s.js} +1 -1
- package/static/app/assets/{brain-xczrohrt.js → brain-CxaeKCRM.js} +1 -1
- package/static/app/assets/{button-SOdH3Oyf.js → button-ZYFqGan7.js} +1 -1
- package/static/app/assets/{circle-pause-CG1ythH4.js → circle-pause-B5NOIqvk.js} +1 -1
- package/static/app/assets/{circle-play-HXwvjS6W.js → circle-play-BiJVFXf7.js} +1 -1
- package/static/app/assets/{cpu-B0d-rGyk.js → cpu-Vvq0OKTr.js} +1 -1
- package/static/app/assets/{download-BGIkTQL6.js → download-SEcXzkx1.js} +1 -1
- package/static/app/assets/{folder-open-Dst_Z0_K.js → folder-open-Dw1ff_Qa.js} +1 -1
- package/static/app/assets/{hard-drive-D53MsWkV.js → hard-drive-B00fR7_r.js} +1 -1
- package/static/app/assets/index-CEkL4_M-.css +2 -0
- package/static/app/assets/{index-C_IrlQMV.js → index-Dm9VXl1o.js} +3 -3
- package/static/app/assets/{input-C5m0riF6.js → input-DtdWYQEa.js} +1 -1
- package/static/app/assets/{network-C5a-E5iS.js → network-CpjBu0kI.js} +1 -1
- package/static/app/assets/{primitives-vNXYf58F.js → primitives-CwsudBwc.js} +1 -1
- package/static/app/assets/search-CwEJWBEA.js +1 -0
- package/static/app/assets/{shield-alert-Cc-WVXqN.js → shield-alert-CpjBc-gQ.js} +1 -1
- package/static/app/assets/{textarea-BkZ0EqVO.js → textarea-DUPyjHQs.js} +1 -1
- package/static/app/assets/{useFocusTrap-CTtKbAOU.js → useFocusTrap-BWcMtCWH.js} +1 -1
- package/static/app/assets/{useQuery-Dx1fi4Wu.js → useQuery-Dg5fPqTS.js} +1 -1
- package/static/app/assets/{users-BFpQXtEF.js → users-BgcdTQGM.js} +1 -1
- package/static/app/assets/{utils-BA_lmW3J.js → utils-a3YXFtEO.js} +2 -2
- package/static/app/assets/workspace-BMUEiXUm.js +1 -0
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/static/app/assets/Library-DhvoPvC7.js +0 -1
- package/static/app/assets/System-CSMdYLMy.js +0 -1
- package/static/app/assets/index-FxDusbr0.css +0 -2
- package/static/app/assets/search-DhbSgW6m.js +0 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
91
|
+
quiet()
|
|
91
92
|
|
|
92
93
|
return {
|
|
93
94
|
"os_type": os_type,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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]:
|
package/lattice_brain/quality.py
CHANGED
|
@@ -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
|
|
15
|
+
from collections import defaultdict
|
|
13
16
|
from dataclasses import dataclass, field
|
|
14
17
|
from typing import Any, Dict, List, Optional
|
|
15
|
-
|
|
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
|
-
|
|
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"]
|