superlocalmemory 4.0.8 → 4.0.9
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/CHANGELOG.md +146 -1
- package/README.md +6 -6
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/scripts/ensure-venv.sh +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +1 -1
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +3 -3
- package/plugin-src/rules/AGENTS.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +2 -2
- package/pyproject.toml +1 -1
- package/scripts/postinstall.js +4 -0
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/_lazy_init.py +1 -1
- package/src/superlocalmemory/cli/commands.py +119 -9
- package/src/superlocalmemory/cli/db_migrate.py +0 -2
- package/src/superlocalmemory/cli/gdpr_io.py +1 -1
- package/src/superlocalmemory/cli/main.py +5 -5
- package/src/superlocalmemory/cli/service_installer.py +2 -1
- package/src/superlocalmemory/cli/setup_wizard.py +1 -1
- package/src/superlocalmemory/core/config.py +41 -7
- package/src/superlocalmemory/core/context_cache.py +0 -2
- package/src/superlocalmemory/core/engine.py +371 -63
- package/src/superlocalmemory/core/evidence_bundle.py +3 -1
- package/src/superlocalmemory/core/install_detector.py +131 -0
- package/src/superlocalmemory/core/progressive_abstraction.py +1 -1
- package/src/superlocalmemory/core/security_primitives.py +3 -6
- package/src/superlocalmemory/core/store_pipeline.py +94 -26
- package/src/superlocalmemory/core/topic_signature.py +0 -2
- package/src/superlocalmemory/core/transactions/concrete_owners.py +15 -8
- package/src/superlocalmemory/dynamics/eap_scheduler.py +17 -6
- package/src/superlocalmemory/encoding/graph_builder.py +2 -2
- package/src/superlocalmemory/encoding/scene_builder.py +8 -2
- package/src/superlocalmemory/hooks/adapter_base.py +0 -2
- package/src/superlocalmemory/hooks/context_payload.py +0 -2
- package/src/superlocalmemory/hooks/portable_kit.py +8 -8
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +0 -2
- package/src/superlocalmemory/hooks/prewarm_auth.py +0 -2
- package/src/superlocalmemory/hooks/user_prompt_hook.py +0 -2
- package/src/superlocalmemory/infra/backup.py +44 -8
- package/src/superlocalmemory/integrations/bounded_loops_mcp.py +24 -7
- package/src/superlocalmemory/learning/arm_catalog.py +0 -2
- package/src/superlocalmemory/learning/bandit.py +0 -2
- package/src/superlocalmemory/learning/bandit_cache.py +0 -2
- package/src/superlocalmemory/learning/dedup_hnsw.py +11 -11
- package/src/superlocalmemory/learning/ensemble.py +0 -2
- package/src/superlocalmemory/learning/labeler.py +0 -2
- package/src/superlocalmemory/learning/legacy_migration.py +0 -2
- package/src/superlocalmemory/learning/model_cache.py +0 -2
- package/src/superlocalmemory/learning/ranker.py +0 -2
- package/src/superlocalmemory/learning/reward_archive.py +6 -1
- package/src/superlocalmemory/learning/reward_proxy.py +0 -2
- package/src/superlocalmemory/learning/signal_worker.py +0 -2
- package/src/superlocalmemory/math/fisher.py +1 -1
- package/src/superlocalmemory/math/hopfield.py +4 -1
- package/src/superlocalmemory/math/langevin.py +1 -1
- package/src/superlocalmemory/math/sheaf.py +7 -3
- package/src/superlocalmemory/mcp/cli_fallback.py +1 -1
- package/src/superlocalmemory/mcp/profiles.py +1 -1
- package/src/superlocalmemory/mcp/server.py +1 -1
- package/src/superlocalmemory/mcp/tools_active.py +56 -0
- package/src/superlocalmemory/mcp/tools_core.py +1 -1
- package/src/superlocalmemory/optimize/cache/manager.py +2 -2
- package/src/superlocalmemory/optimize/compress/ccr.py +1 -1
- package/src/superlocalmemory/optimize/compress/router.py +1 -1
- package/src/superlocalmemory/optimize/proxy/_helpers.py +2 -2
- package/src/superlocalmemory/optimize/proxy/server.py +1 -1
- package/src/superlocalmemory/optimize/proxy/vertex_surface.py +2 -2
- package/src/superlocalmemory/optimize/storage/db.py +2 -2
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/ann_index.py +9 -2
- package/src/superlocalmemory/retrieval/bm25_channel.py +2 -2
- package/src/superlocalmemory/retrieval/bridge_discovery.py +2 -2
- package/src/superlocalmemory/retrieval/engine.py +272 -43
- package/src/superlocalmemory/retrieval/entity_channel.py +1 -1
- package/src/superlocalmemory/retrieval/hopfield_channel.py +8 -2
- package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
- package/src/superlocalmemory/retrieval/quantization_aware_search.py +1 -1
- package/src/superlocalmemory/retrieval/remote_reranker.py +2 -2
- package/src/superlocalmemory/retrieval/reranker.py +3 -3
- package/src/superlocalmemory/retrieval/semantic_channel.py +3 -3
- package/src/superlocalmemory/retrieval/spreading_activation.py +8 -8
- package/src/superlocalmemory/retrieval/strategy.py +94 -0
- package/src/superlocalmemory/retrieval/temporal_channel.py +167 -10
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +1 -1
- package/src/superlocalmemory/retrieval/vector_store.py +88 -10
- package/src/superlocalmemory/server/recall_serializer.py +10 -0
- package/src/superlocalmemory/server/routes/brain.py +2 -2
- package/src/superlocalmemory/server/routes/prewarm.py +4 -4
- package/src/superlocalmemory/server/unified_daemon.py +459 -7
- package/src/superlocalmemory/storage/_schema_version.py +46 -3
- package/src/superlocalmemory/storage/backup.py +531 -0
- package/src/superlocalmemory/storage/database.py +11 -4
- package/src/superlocalmemory/storage/embedding_codec.py +129 -0
- package/src/superlocalmemory/storage/embedding_migrator.py +5 -3
- package/src/superlocalmemory/storage/migration_runner.py +142 -2
- package/src/superlocalmemory/storage/migrations/__init__.py +1 -1
- package/src/superlocalmemory/storage/migrations.py +15 -1
- package/src/superlocalmemory/storage/models.py +7 -0
- package/src/superlocalmemory/storage/quantized_store.py +4 -2
- package/src/superlocalmemory/summaries/session_summary.py +1 -1
- package/src/superlocalmemory/ui/css/legacy-dashboard.css +1 -1
- package/src/superlocalmemory/ui/css/neural-glass.css +1 -1
- package/src/superlocalmemory/ui/js/core.js +1 -1
- package/src/superlocalmemory/ui/js/od-brain.js +1 -1
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
"""Active-model cache + integrity verification.
|
|
6
6
|
|
|
7
|
-
LLD reference: ``.backup/active-brain/lld/LLD-02-signal-pipeline-and-lightgbm.md``
|
|
8
|
-
Section 4.4 — every model load goes through here.
|
|
9
7
|
|
|
10
8
|
Hard rules enforced:
|
|
11
9
|
M1 — ``pickle.loads`` is FORBIDDEN on ``state_bytes``.
|
|
@@ -34,6 +34,7 @@ from pathlib import Path
|
|
|
34
34
|
from superlocalmemory.learning.fact_outcome_joins import (
|
|
35
35
|
has_recent_positive_reward,
|
|
36
36
|
)
|
|
37
|
+
from superlocalmemory.storage.embedding_codec import decode_embedding
|
|
37
38
|
from superlocalmemory.storage.write_lock import get_write_lock
|
|
38
39
|
|
|
39
40
|
logger = logging.getLogger(__name__)
|
|
@@ -113,13 +114,17 @@ def run_reward_gated_archive(
|
|
|
113
114
|
window_days=REWARD_WINDOW_DAYS,
|
|
114
115
|
):
|
|
115
116
|
continue
|
|
117
|
+
# Decode the embedding to list[float] so json.dumps(payload)
|
|
118
|
+
# succeeds regardless of whether the row is TEXT or BLOB.
|
|
119
|
+
# ValueError (corrupt buffer) propagates — a silent None here
|
|
120
|
+
# would archive a fact with a missing embedding, which loses data.
|
|
116
121
|
to_archive.append({
|
|
117
122
|
"fid": fid,
|
|
118
123
|
"content": row["content"],
|
|
119
124
|
"canonical_entities_json": row["canonical_entities_json"],
|
|
120
125
|
"importance": row["importance"],
|
|
121
126
|
"confidence": row["confidence"],
|
|
122
|
-
"embedding": row["embedding"],
|
|
127
|
+
"embedding": decode_embedding(row["embedding"], fact_id=fid),
|
|
123
128
|
"created_at": row["created_at"],
|
|
124
129
|
})
|
|
125
130
|
finally:
|
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
"""Proxy settlement for bandit plays (v3.4.22 only).
|
|
6
6
|
|
|
7
|
-
LLD reference: ``.backup/active-brain/lld/LLD-03-contextual-bandit-and-ensemble.md``
|
|
8
|
-
Section 3.5 and 5.6.
|
|
9
7
|
|
|
10
8
|
Replaced in v3.4.22 by ``reward_from_outcomes.py`` — DO NOT extend this
|
|
11
9
|
module beyond the proxy window contract.
|
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
"""Background signal drain worker.
|
|
6
6
|
|
|
7
|
-
LLD reference: ``.backup/active-brain/lld/LLD-02-signal-pipeline-and-lightgbm.md``
|
|
8
|
-
Section 4.2 — moves signal writes off the recall hot path.
|
|
9
7
|
|
|
10
8
|
Contract (hard rules, enforced by tests):
|
|
11
9
|
SW1 — Hot path never waits for disk.
|
|
@@ -19,7 +19,7 @@ Geodesic distance (Atkinson & Mitchell 1981, Pinele et al. 2020):
|
|
|
19
19
|
Diagonal multivariate (product-manifold decomposition):
|
|
20
20
|
d_FR(p, q) = sqrt( sum_i d_i^2 )
|
|
21
21
|
|
|
22
|
-
Bayesian variance update (
|
|
22
|
+
Bayesian variance update (added in v3.5):
|
|
23
23
|
|
|
24
24
|
V1 bug: query always received UNIFORM variance, so Fisher degenerated
|
|
25
25
|
to a monotonic transform of cosine. FIX: every fact maintains its own
|
|
@@ -56,7 +56,10 @@ class HopfieldConfig:
|
|
|
56
56
|
max_iterations: int = 1
|
|
57
57
|
convergence_epsilon: float = 1e-6
|
|
58
58
|
prefilter_threshold: int = 10_000
|
|
59
|
-
prefilter_candidates
|
|
59
|
+
# MUST match HopfieldConfig.prefilter_candidates in core/config.py, where the
|
|
60
|
+
# reasoning for the value is written. This stage decides final membership, so
|
|
61
|
+
# this number is a hard limit on which memories can be returned at all.
|
|
62
|
+
prefilter_candidates: int = 500
|
|
60
63
|
skip_threshold: int = 100_000
|
|
61
64
|
cache_ttl_seconds: float = 60.0
|
|
62
65
|
|
|
@@ -26,7 +26,7 @@ Potential function:
|
|
|
26
26
|
- Old / unused: gamma term raises potential -> drift to boundary
|
|
27
27
|
- Important memories: delta term lowers potential -> retain near origin
|
|
28
28
|
|
|
29
|
-
V1 bugs fixed in
|
|
29
|
+
V1 bugs fixed in v3.5:
|
|
30
30
|
1. Positions were computed per-recall then DISCARDED. Now ``step()``
|
|
31
31
|
and ``batch_step()`` return new positions for the caller to persist.
|
|
32
32
|
2. Weight range was [0.7, 1.0] --- too narrow to change rankings.
|
|
@@ -24,6 +24,8 @@ from typing import TYPE_CHECKING
|
|
|
24
24
|
|
|
25
25
|
import numpy as np
|
|
26
26
|
|
|
27
|
+
from superlocalmemory.storage.embedding_codec import decode_embedding
|
|
28
|
+
|
|
27
29
|
if TYPE_CHECKING:
|
|
28
30
|
from superlocalmemory.storage.database import DatabaseManager
|
|
29
31
|
from superlocalmemory.storage.models import AtomicFact
|
|
@@ -251,7 +253,9 @@ class SheafConsistencyChecker:
|
|
|
251
253
|
if raw is None or raw == "":
|
|
252
254
|
return None
|
|
253
255
|
try:
|
|
254
|
-
data =
|
|
255
|
-
|
|
256
|
-
|
|
256
|
+
data = decode_embedding(raw, fact_id=str(fact_id))
|
|
257
|
+
except ValueError:
|
|
258
|
+
return None
|
|
259
|
+
if data is None:
|
|
257
260
|
return None
|
|
261
|
+
return np.asarray(data, dtype=np.float64)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
2
|
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
-
#
|
|
3
|
+
# MCP→CLI fallback adapter (ships inert — disabled by default, no active use yet)
|
|
4
4
|
|
|
5
5
|
"""MCP→CLI fallback adapter for SuperLocalMemory.
|
|
6
6
|
|
|
@@ -14,7 +14,7 @@ Do NOT import FastMCP, MemoryEngine, or any heavy dependency here.
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
# ---------------------------------------------------------------------------
|
|
17
|
-
# v3.6.14
|
|
17
|
+
# Named profile definitions (introduced in v3.6.14)
|
|
18
18
|
# ---------------------------------------------------------------------------
|
|
19
19
|
|
|
20
20
|
_PROFILE_CORE: frozenset[str] = frozenset({ # 17
|
|
@@ -159,7 +159,7 @@ _all_tools = _os_reg.environ.get("SLM_MCP_ALL_TOOLS") == "1"
|
|
|
159
159
|
_user_allowlist_str = _os_reg.environ.get("SLM_MCP_TOOLS", "").strip()
|
|
160
160
|
|
|
161
161
|
# ---------------------------------------------------------------------------
|
|
162
|
-
# v3.6.14
|
|
162
|
+
# Named profile definitions (introduced in v3.6.14)
|
|
163
163
|
# Extracted to mcp/profiles.py (v3.8.0) — pure data, no side effects.
|
|
164
164
|
# All names re-exported here for backward compatibility with existing tests
|
|
165
165
|
# and any code that imports them from this module.
|
|
@@ -255,6 +255,56 @@ def _canonical_feedback_count(profile_id: str) -> int | None:
|
|
|
255
255
|
return None
|
|
256
256
|
|
|
257
257
|
|
|
258
|
+
# How far ahead a session looks for scheduled facts, and how many it shows.
|
|
259
|
+
# A session preamble is not a calendar: a long horizon or a large cap turns a
|
|
260
|
+
# useful heads-up into a wall of text nobody reads.
|
|
261
|
+
_SCHEDULED_HORIZON_DAYS = 14
|
|
262
|
+
_SCHEDULED_LIMIT = 5
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _upcoming_scheduled_facts(engine, now: datetime.datetime) -> list[dict]:
|
|
266
|
+
"""Facts scheduled from today through the horizon, soonest first.
|
|
267
|
+
|
|
268
|
+
Bounded and index-backed, because this runs on every session start. Returns
|
|
269
|
+
an empty list on any failure: a session must still open when this query
|
|
270
|
+
cannot answer, so the caller omits the surface entirely rather than showing
|
|
271
|
+
an empty section that reads like a defect.
|
|
272
|
+
|
|
273
|
+
Selects on the stored type of the fact, which is a classification recorded at
|
|
274
|
+
write time. It is unrelated to the similarly named retrieval channel despite
|
|
275
|
+
sharing the word.
|
|
276
|
+
"""
|
|
277
|
+
try:
|
|
278
|
+
db = getattr(engine, "_db", None) or getattr(engine, "db", None)
|
|
279
|
+
if db is None or not hasattr(db, "execute"):
|
|
280
|
+
return []
|
|
281
|
+
# Both 'YYYY-MM-DD' and full timestamps compare correctly as text,
|
|
282
|
+
# because ISO-8601 orders lexicographically. The upper bound is
|
|
283
|
+
# exclusive, so the horizon day itself is included.
|
|
284
|
+
start = now.date().isoformat()
|
|
285
|
+
end = (now + datetime.timedelta(days=_SCHEDULED_HORIZON_DAYS + 1)).date().isoformat()
|
|
286
|
+
rows = db.execute(
|
|
287
|
+
"SELECT fact_id, content, referenced_date"
|
|
288
|
+
" FROM atomic_facts"
|
|
289
|
+
" WHERE profile_id = ?"
|
|
290
|
+
" AND fact_type = 'temporal'"
|
|
291
|
+
" AND referenced_date IS NOT NULL"
|
|
292
|
+
" AND referenced_date >= ?"
|
|
293
|
+
" AND referenced_date < ?"
|
|
294
|
+
" ORDER BY referenced_date ASC"
|
|
295
|
+
f" LIMIT {_SCHEDULED_LIMIT}",
|
|
296
|
+
(engine.profile_id, start, end),
|
|
297
|
+
)
|
|
298
|
+
return [
|
|
299
|
+
{"fact_id": r["fact_id"], "content": r["content"],
|
|
300
|
+
"scheduled_at": r["referenced_date"]}
|
|
301
|
+
for r in rows
|
|
302
|
+
]
|
|
303
|
+
except Exception as exc:
|
|
304
|
+
logger.warning("scheduled-fact surface failed: %s", exc)
|
|
305
|
+
return []
|
|
306
|
+
|
|
307
|
+
|
|
258
308
|
def register_active_tools(server, get_engine: Callable) -> None:
|
|
259
309
|
"""Register 3 active memory tools on *server*."""
|
|
260
310
|
|
|
@@ -519,6 +569,8 @@ def register_active_tools(server, get_engine: Callable) -> None:
|
|
|
519
569
|
f"-{uuid.uuid4().hex[:8]}"
|
|
520
570
|
)
|
|
521
571
|
|
|
572
|
+
_upcoming_events = _upcoming_scheduled_facts(engine, _now)
|
|
573
|
+
|
|
522
574
|
return {
|
|
523
575
|
"success": True,
|
|
524
576
|
"session_id": session_id,
|
|
@@ -553,6 +605,10 @@ def register_active_tools(server, get_engine: Callable) -> None:
|
|
|
553
605
|
else "trained"
|
|
554
606
|
),
|
|
555
607
|
},
|
|
608
|
+
# Scheduled-event surface: present only when facts exist in the
|
|
609
|
+
# 14-day window. Absent means the window is empty, not an error.
|
|
610
|
+
**( {"upcoming_events": _upcoming_events}
|
|
611
|
+
if _upcoming_events else {} ),
|
|
556
612
|
}
|
|
557
613
|
except Exception as exc:
|
|
558
614
|
logger.exception("session_init failed")
|
|
@@ -560,7 +560,7 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
560
560
|
if db_path.exists():
|
|
561
561
|
db_size_mb = round(os.path.getsize(db_path) / (1024 * 1024), 2)
|
|
562
562
|
|
|
563
|
-
#
|
|
563
|
+
# additive canonical key set — provider/base_dir/db_path added.
|
|
564
564
|
# All pre-existing keys are preserved (zero removals).
|
|
565
565
|
cfg = engine._config
|
|
566
566
|
return {
|
|
@@ -223,7 +223,7 @@ class CacheManager:
|
|
|
223
223
|
tenant_id = _hashlib.sha256(tenant_id.encode()).hexdigest()
|
|
224
224
|
|
|
225
225
|
if isinstance(req, ProxyRequest) and req.provider == "vertex":
|
|
226
|
-
#
|
|
226
|
+
# LOCKED: Vertex bodies have NO model/messages/system.
|
|
227
227
|
# Model is in the PATH; prompts are under 'contents'; system under
|
|
228
228
|
# 'systemInstruction'. Without this branch ALL Vertex requests hash to
|
|
229
229
|
# ONE key → first response poisons every subsequent prompt.
|
|
@@ -701,7 +701,7 @@ def json_dumps_bytes(d: dict) -> bytes:
|
|
|
701
701
|
|
|
702
702
|
|
|
703
703
|
# ---------------------------------------------------------------------------
|
|
704
|
-
# Vertex helpers
|
|
704
|
+
# Vertex helpers
|
|
705
705
|
# ---------------------------------------------------------------------------
|
|
706
706
|
|
|
707
707
|
def _vertex_project_location_from_path(path: str) -> tuple[str, str]:
|
|
@@ -99,7 +99,7 @@ class CCRStore:
|
|
|
99
99
|
def delete(self, ccr_id: str, *, tenant_id: str = "default") -> None:
|
|
100
100
|
"""Delete a CCR row by ccr_id scoped to tenant. Idempotent — never raises.
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
Defensive delete — idempotent: deleting a non-existent ccr_id is a no-op.
|
|
103
103
|
H-02: tenant_id guard prevents cross-tenant deletion.
|
|
104
104
|
"""
|
|
105
105
|
try:
|
|
@@ -411,7 +411,7 @@ class CompressRouter:
|
|
|
411
411
|
logger.debug("CCR update_compressed failed (non-fatal): %s", exc)
|
|
412
412
|
|
|
413
413
|
def _ccr_delete(self, ccr_id: str) -> None:
|
|
414
|
-
"""
|
|
414
|
+
"""Defensive delete — idempotent, never raises.
|
|
415
415
|
|
|
416
416
|
Used to clean up a CCR row if post-store processing fails. In the
|
|
417
417
|
store-after-success D6 path this should never be needed (no orphans
|
|
@@ -123,10 +123,10 @@ _OPENAI_FORWARD_HEADERS = frozenset([
|
|
|
123
123
|
_GEMINI_NATIVE_FORWARD_HEADERS = frozenset([
|
|
124
124
|
"x-goog-api-key",
|
|
125
125
|
"content-type",
|
|
126
|
-
"authorization", #
|
|
126
|
+
"authorization", # Antigravity ADC/OAuth bearer was previously dropped; explicitly add it back
|
|
127
127
|
])
|
|
128
128
|
|
|
129
|
-
#
|
|
129
|
+
# Vertex AI forward headers — Authorization is passed through untouched.
|
|
130
130
|
# x-goog-user-project required for quota attribution on Vertex calls.
|
|
131
131
|
_VERTEX_FORWARD_HEADERS = frozenset([
|
|
132
132
|
"authorization",
|
|
@@ -150,7 +150,7 @@ def build_proxy_router(proxy: ProxyApp) -> APIRouter:
|
|
|
150
150
|
async def gemini_openai_models_route(request: Request) -> Response:
|
|
151
151
|
return await handle_gemini_openai_compat(proxy, request)
|
|
152
152
|
|
|
153
|
-
#
|
|
153
|
+
# Vertex AI passthrough — must be registered AFTER exact /v1/* routes
|
|
154
154
|
# to avoid shadowing /v1/messages, /v1/chat/completions, /v1/embeddings, etc.
|
|
155
155
|
# FastAPI resolves routes in registration order; the exact routes above are
|
|
156
156
|
# declared before this catch-path, so there is no shadowing.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""vertex_surface.py — Vertex AI passthrough proxy surface
|
|
1
|
+
"""vertex_surface.py — Vertex AI passthrough proxy surface.
|
|
2
2
|
|
|
3
3
|
Transparent passthrough: forward Authorization bearer untouched (AC-2),
|
|
4
4
|
cache by body content never by token (SEC), no SSE — always single JSON.
|
|
@@ -14,7 +14,7 @@ Key design decisions (per LLD §5 STAGE-5 RESOLUTIONS):
|
|
|
14
14
|
SECURITY (AC-3): bearer token structurally excluded from cache key, value,
|
|
15
15
|
logs, and stored ProxyRequest.headers (redacted via _redact_headers).
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
The gemini-native Authorization-header fix lives in _helpers.py:_GEMINI_NATIVE_FORWARD_HEADERS.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
from __future__ import annotations
|
|
@@ -959,7 +959,7 @@ class CacheDB:
|
|
|
959
959
|
def ccr_delete(self, ccr_id: str, *, tenant_id: str = "default") -> None:
|
|
960
960
|
"""Delete a CCR row by ccr_id scoped to tenant. Idempotent — warns on sqlite error, never raises.
|
|
961
961
|
|
|
962
|
-
|
|
962
|
+
Defensive delete — idempotent: deleting a non-existent row is a no-op.
|
|
963
963
|
H-02: tenant_id guard prevents a tenant from deleting another tenant's CCR.
|
|
964
964
|
"""
|
|
965
965
|
try:
|
|
@@ -973,7 +973,7 @@ class CacheDB:
|
|
|
973
973
|
def ccr_count(self) -> int:
|
|
974
974
|
"""Return UNFILTERED count of rows in llmcache_ccr_originals.
|
|
975
975
|
|
|
976
|
-
|
|
976
|
+
Do NOT reuse TTL-filtered count from the query above. A fresh no-expiry row
|
|
977
977
|
has ttl_expires=None, so the TTL filter returns 0 and D6 orphan tests would
|
|
978
978
|
falsely pass. This unfiltered count is test infrastructure only.
|
|
979
979
|
"""
|
|
@@ -163,7 +163,7 @@ class AgenticRetriever:
|
|
|
163
163
|
RetrievalRound(2, rq, len(rn), _avg(rn), True),
|
|
164
164
|
)
|
|
165
165
|
|
|
166
|
-
merged = sorted(pool.values(), key=lambda x: x[1],
|
|
166
|
+
merged = sorted(pool.values(), key=lambda x: (-x[1], x[0].fact_id))
|
|
167
167
|
return [f for f, _ in merged[:top_k]]
|
|
168
168
|
|
|
169
169
|
# -- Sufficiency check ---------------------------------------------------
|
|
@@ -178,8 +178,15 @@ class ANNIndex:
|
|
|
178
178
|
if k <= 0:
|
|
179
179
|
return []
|
|
180
180
|
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
# Full sort with a secondary key on fact_id so that equal cosine
|
|
182
|
+
# scores are broken deterministically. argpartition cannot
|
|
183
|
+
# guarantee which facts reach the top-k when scores tie at the
|
|
184
|
+
# boundary — the selection follows array layout (insertion order)
|
|
185
|
+
# and changes between restarts. lexsort picks the same k facts
|
|
186
|
+
# in the same order regardless of how the index was loaded.
|
|
187
|
+
id_arr = np.array(self._ids, dtype=object)
|
|
188
|
+
order = np.lexsort((id_arr, -scores)) # primary: -score, secondary: fact_id
|
|
189
|
+
top_indices = order[:k]
|
|
183
190
|
|
|
184
191
|
return [
|
|
185
192
|
(self._ids[i], float(scores[i]))
|
|
@@ -267,7 +267,7 @@ class BM25Channel:
|
|
|
267
267
|
except Exception as exc: # pragma: no cover — legacy/missing expansion FTS
|
|
268
268
|
logger.debug("Expansion FTS search skipped: %s", exc)
|
|
269
269
|
|
|
270
|
-
out.sort(key=lambda x: x[1],
|
|
270
|
+
out.sort(key=lambda x: (-x[1], x[0]))
|
|
271
271
|
return out[:top_k]
|
|
272
272
|
|
|
273
273
|
def search(
|
|
@@ -341,7 +341,7 @@ class BM25Channel:
|
|
|
341
341
|
bonus *= 1.5 # 50% boost for exact phrase match
|
|
342
342
|
scored.append((self._fact_ids[i], bonus))
|
|
343
343
|
|
|
344
|
-
scored.sort(key=lambda x: x[1],
|
|
344
|
+
scored.sort(key=lambda x: (-x[1], x[0]))
|
|
345
345
|
return scored[:top_k]
|
|
346
346
|
|
|
347
347
|
def update_fact(self, fact_id: str, new_content: str, profile_id: str) -> None:
|
|
@@ -160,7 +160,7 @@ class BridgeDiscovery:
|
|
|
160
160
|
if len(bridges) >= max_bridges:
|
|
161
161
|
break
|
|
162
162
|
|
|
163
|
-
bridges.sort(key=lambda x: x[1],
|
|
163
|
+
bridges.sort(key=lambda x: (-x[1], x[0]))
|
|
164
164
|
return filter_authorized_results(
|
|
165
165
|
self._db,
|
|
166
166
|
bridges,
|
|
@@ -243,7 +243,7 @@ class BridgeDiscovery:
|
|
|
243
243
|
for fid, score in activations.items()
|
|
244
244
|
if fid not in set(seed_ids) and score > 0.01
|
|
245
245
|
]
|
|
246
|
-
results.sort(key=lambda x: x[1],
|
|
246
|
+
results.sort(key=lambda x: (-x[1], x[0]))
|
|
247
247
|
return filter_authorized_results(
|
|
248
248
|
self._db,
|
|
249
249
|
results,
|