superlocalmemory 3.8.13 → 4.0.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/ATTRIBUTION.md +4 -4
- package/CHANGELOG.md +113 -121
- package/README.md +65 -63
- package/docs/pi-dev-integration.md +1 -1
- package/package.json +6 -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/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 +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-governance/SKILL.md +248 -0
- package/plugin-src/skills/slm-graph/SKILL.md +1 -1
- package/plugin-src/skills/slm-loop/SKILL.md +99 -0
- package/plugin-src/skills/slm-mesh/SKILL.md +282 -0
- package/plugin-src/skills/slm-profile/SKILL.md +148 -0
- package/plugin-src/skills/slm-recall/SKILL.md +1 -1
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-scope/SKILL.md +176 -0
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/pyproject.toml +11 -4
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +125 -11
- package/src/superlocalmemory/cli/daemon.py +5 -1
- package/src/superlocalmemory/cli/main.py +35 -2
- package/src/superlocalmemory/cli/ops_cmd.py +281 -0
- package/src/superlocalmemory/cli/setup_wizard.py +1 -1
- package/src/superlocalmemory/compliance/audit.py +65 -0
- package/src/superlocalmemory/compliance/eu_ai_act.py +27 -57
- package/src/superlocalmemory/compliance/gdpr.py +416 -20
- package/src/superlocalmemory/compliance/retention.py +74 -22
- package/src/superlocalmemory/compliance/scheduler.py +78 -9
- package/src/superlocalmemory/core/actor_context.py +166 -0
- package/src/superlocalmemory/core/admission.py +549 -0
- package/src/superlocalmemory/core/backend_orchestrator.py +23 -10
- package/src/superlocalmemory/core/config.py +202 -24
- package/src/superlocalmemory/core/consolidation_engine.py +13 -13
- package/src/superlocalmemory/core/context_cache.py +28 -0
- package/src/superlocalmemory/core/embeddings.py +64 -2
- package/src/superlocalmemory/core/engine.py +7 -2
- package/src/superlocalmemory/core/engine_ingestion.py +65 -3
- package/src/superlocalmemory/core/engine_wiring.py +36 -9
- package/src/superlocalmemory/core/ingest_policy.py +38 -0
- package/src/superlocalmemory/core/maintenance.py +255 -0
- package/src/superlocalmemory/core/modes.py +40 -13
- package/src/superlocalmemory/core/mutations.py +437 -44
- package/src/superlocalmemory/core/operation_policy.py +92 -0
- package/src/superlocalmemory/core/operation_policy_registry.py +542 -0
- package/src/superlocalmemory/core/operation_request.py +127 -0
- package/src/superlocalmemory/core/ops_remediation.py +542 -0
- package/src/superlocalmemory/core/recall_pipeline.py +7 -0
- package/src/superlocalmemory/core/remember_runtime.py +202 -4
- package/src/superlocalmemory/core/remote_mode.py +20 -5
- package/src/superlocalmemory/core/store_pipeline.py +150 -0
- package/src/superlocalmemory/core/topic_signature.py +19 -4
- package/src/superlocalmemory/core/transactions/__init__.py +78 -0
- package/src/superlocalmemory/core/transactions/concrete_owners.py +597 -0
- package/src/superlocalmemory/core/transactions/erasure.py +825 -0
- package/src/superlocalmemory/core/transactions/manifest.py +255 -0
- package/src/superlocalmemory/core/transactions/manifest_key.py +155 -0
- package/src/superlocalmemory/core/transactions/obligations.py +272 -0
- package/src/superlocalmemory/core/transactions/owners.py +114 -0
- package/src/superlocalmemory/core/transactions/reconciler.py +285 -0
- package/src/superlocalmemory/core/transactions/service.py +330 -0
- package/src/superlocalmemory/core/worker_pool.py +33 -5
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +70 -28
- package/src/superlocalmemory/encoding/emotional.py +75 -14
- package/src/superlocalmemory/encoding/scene_builder.py +115 -13
- package/src/superlocalmemory/encoding/temporal_parser.py +4 -0
- package/src/superlocalmemory/evolution/blind_verifier.py +11 -4
- package/src/superlocalmemory/evolution/evolution_store.py +244 -4
- package/src/superlocalmemory/evolution/llm_dispatch.py +40 -0
- package/src/superlocalmemory/evolution/model_selection.py +18 -3
- package/src/superlocalmemory/evolution/mutation_generator.py +3 -0
- package/src/superlocalmemory/evolution/skill_activator.py +270 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +281 -59
- package/src/superlocalmemory/evolution/types.py +30 -8
- package/src/superlocalmemory/graph/cozo_backend.py +17 -9
- package/src/superlocalmemory/hooks/auto_invoker.py +2 -1
- package/src/superlocalmemory/hooks/auto_recall.py +64 -30
- package/src/superlocalmemory/hooks/codex_assets.py +14 -1
- package/src/superlocalmemory/infra/backup.py +434 -7
- package/src/superlocalmemory/infra/process_reaper.py +18 -0
- package/src/superlocalmemory/infra/self_heal.py +401 -0
- package/src/superlocalmemory/learning/feedback.py +52 -9
- package/src/superlocalmemory/loops/engine.py +10 -0
- package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
- package/src/superlocalmemory/mcp/http_transport.py +30 -331
- package/src/superlocalmemory/mcp/profiles.py +5 -0
- package/src/superlocalmemory/mcp/resources.py +8 -0
- package/src/superlocalmemory/mcp/server.py +51 -4
- package/src/superlocalmemory/mcp/shared.py +19 -0
- package/src/superlocalmemory/mcp/tools_active.py +25 -4
- package/src/superlocalmemory/mcp/tools_code_graph.py +26 -18
- package/src/superlocalmemory/mcp/tools_context.py +50 -8
- package/src/superlocalmemory/mcp/tools_core.py +69 -21
- package/src/superlocalmemory/mcp/tools_evolution.py +9 -2
- package/src/superlocalmemory/mcp/tools_learning.py +21 -10
- package/src/superlocalmemory/mcp/tools_loops.py +29 -18
- package/src/superlocalmemory/mcp/tools_mesh.py +8 -0
- package/src/superlocalmemory/mcp/tools_ops.py +115 -0
- package/src/superlocalmemory/mcp/tools_optimize.py +4 -0
- package/src/superlocalmemory/mcp/tools_v28.py +10 -3
- package/src/superlocalmemory/mcp/tools_v3.py +34 -14
- package/src/superlocalmemory/mcp/tools_v33.py +18 -33
- package/src/superlocalmemory/mesh/broker.py +124 -46
- package/src/superlocalmemory/mesh/broker_security.py +470 -0
- package/src/superlocalmemory/mesh/discovery.py +365 -0
- package/src/superlocalmemory/mesh/lock_protocol.py +313 -0
- package/src/superlocalmemory/mesh/node_identity.py +97 -0
- package/src/superlocalmemory/mesh/outbox_remote.py +429 -0
- package/src/superlocalmemory/mesh/remote_sync.py +511 -28
- package/src/superlocalmemory/mesh/state_sync.py +286 -0
- package/src/superlocalmemory/optimize/config/store.py +45 -0
- package/src/superlocalmemory/parameterization/cross_project.py +12 -0
- package/src/superlocalmemory/parameterization/prompt_injector.py +13 -11
- package/src/superlocalmemory/parameterization/prompt_lifecycle.py +8 -2
- package/src/superlocalmemory/parameterization/workflow_miner.py +17 -0
- package/src/superlocalmemory/retrieval/ann_index.py +5 -0
- package/src/superlocalmemory/retrieval/bm25_channel.py +49 -2
- package/src/superlocalmemory/retrieval/engine.py +19 -4
- package/src/superlocalmemory/retrieval/fusion.py +4 -1
- package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -3
- package/src/superlocalmemory/retrieval/remote_reranker.py +47 -22
- package/src/superlocalmemory/retrieval/reranker.py +32 -1
- package/src/superlocalmemory/retrieval/temporal_channel.py +16 -3
- package/src/superlocalmemory/retrieval/temporal_utils.py +107 -0
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +155 -42
- package/src/superlocalmemory/retrieval/vector_store.py +214 -8
- package/src/superlocalmemory/server/api.py +5 -5
- package/src/superlocalmemory/server/egress_policy.py +258 -0
- package/src/superlocalmemory/server/rbac_enforce.py +32 -0
- package/src/superlocalmemory/server/route_mutations.py +20 -0
- package/src/superlocalmemory/server/routes/compliance.py +153 -7
- package/src/superlocalmemory/server/routes/data_io.py +43 -2
- package/src/superlocalmemory/server/routes/events.py +15 -0
- package/src/superlocalmemory/server/routes/memories.py +56 -3
- package/src/superlocalmemory/server/routes/mesh.py +82 -1
- package/src/superlocalmemory/server/routes/mesh_lock.py +54 -0
- package/src/superlocalmemory/server/routes/mesh_state.py +63 -0
- package/src/superlocalmemory/server/routes/v3_api.py +50 -27
- package/src/superlocalmemory/server/routes/ws.py +86 -0
- package/src/superlocalmemory/server/ui.py +6 -6
- package/src/superlocalmemory/server/unified_daemon.py +942 -119
- package/src/superlocalmemory/storage/_migration_internals.py +568 -0
- package/src/superlocalmemory/storage/_schema_version.py +110 -0
- package/src/superlocalmemory/storage/database.py +329 -24
- package/src/superlocalmemory/storage/embedding_migrator.py +246 -51
- package/src/superlocalmemory/storage/erasure_fence.py +45 -0
- package/src/superlocalmemory/storage/generation_fence.py +63 -0
- package/src/superlocalmemory/storage/migration_runner.py +140 -417
- package/src/superlocalmemory/storage/migrations/M009_model_lineage.py +40 -0
- package/src/superlocalmemory/storage/migrations/M033_projection_transactions.py +148 -0
- package/src/superlocalmemory/storage/migrations/M034_obligation_integrity.py +58 -0
- package/src/superlocalmemory/storage/migrations/M035_erasure_receipts.py +113 -0
- package/src/superlocalmemory/storage/migrations/M036_vector_row_map.py +107 -0
- package/src/superlocalmemory/storage/migrations/M037_manifest_hmac_version.py +162 -0
- package/src/superlocalmemory/storage/migrations/{M033_learning_feedback_channel.py → M038_learning_feedback_channel.py} +3 -3
- package/src/superlocalmemory/storage/migrations/M039_scene_fact_members.py +137 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +4 -2
- package/src/superlocalmemory/storage/schema.py +67 -0
- package/src/superlocalmemory/storage/write_coordinator.py +125 -0
- package/src/superlocalmemory/trust/scorer.py +28 -4
- package/src/superlocalmemory/ui/index.html +14 -3
- package/src/superlocalmemory/ui/js/auto-settings.js +12 -1
- package/src/superlocalmemory/ui/js/brain.js +6 -4
- package/src/superlocalmemory/ui/js/compliance.js +66 -12
- package/src/superlocalmemory/ui/js/dashboard.js +13 -3
- package/src/superlocalmemory/ui/js/feedback.js +8 -2
- package/src/superlocalmemory/ui/js/lifecycle.js +7 -1
- package/src/superlocalmemory/ui/js/modal.js +272 -5
- package/src/superlocalmemory/ui/js/od-backup.js +9 -2
- package/src/superlocalmemory/ui/js/od-compliance-ext.js +301 -0
- package/src/superlocalmemory/ui/js/od-operations.js +154 -23
- package/src/superlocalmemory/ui/js/od-ops-health.js +417 -0
- package/src/superlocalmemory/ui/js/od-optimize.js +35 -21
- package/src/superlocalmemory/ui/js/od-team.js +9 -2
- package/src/superlocalmemory/ui/js/optimize.js +13 -16
- package/src/superlocalmemory/ui/js/profiles.js +7 -3
- package/src/superlocalmemory/ui/js/settings.js +7 -1
- package/src/superlocalmemory/vector/lancedb_backend.py +19 -9
- package/src/superlocalmemory/attribution/mathematical_dna.py +0 -235
- package/src/superlocalmemory/cli/post_install.py +0 -114
- package/src/superlocalmemory/core/clock_monitor.py +0 -45
- package/src/superlocalmemory/core/db_pool.py +0 -80
- package/src/superlocalmemory/core/error_catalog.py +0 -113
- package/src/superlocalmemory/core/loop_watchdog.py +0 -56
- package/src/superlocalmemory/core/priority_queue.py +0 -61
- package/src/superlocalmemory/core/pruning_engine.py +0 -216
- package/src/superlocalmemory/core/queue_dispatcher.py +0 -73
- package/src/superlocalmemory/core/slmignore.py +0 -125
- package/src/superlocalmemory/infra/heartbeat_monitor.py +0 -140
- package/src/superlocalmemory/infra/webhook_dispatcher.py +0 -247
- package/src/superlocalmemory/learning/quantization_scheduler.py +0 -320
- package/src/superlocalmemory/storage/access_control.py +0 -182
|
@@ -15,6 +15,7 @@ from __future__ import annotations
|
|
|
15
15
|
|
|
16
16
|
import hashlib
|
|
17
17
|
import json
|
|
18
|
+
import logging
|
|
18
19
|
import re
|
|
19
20
|
import threading
|
|
20
21
|
import uuid
|
|
@@ -36,6 +37,14 @@ from superlocalmemory.core.remember_admission import (
|
|
|
36
37
|
RememberReceipt,
|
|
37
38
|
RememberService,
|
|
38
39
|
)
|
|
40
|
+
from superlocalmemory.core.transactions.concrete_owners import (
|
|
41
|
+
REQUIRED_ADMISSION_OWNERS,
|
|
42
|
+
)
|
|
43
|
+
from superlocalmemory.core.transactions.obligations import ObligationLedger
|
|
44
|
+
from superlocalmemory.core.transactions.owners import (
|
|
45
|
+
ObligationKind,
|
|
46
|
+
OperationContext,
|
|
47
|
+
)
|
|
39
48
|
from superlocalmemory.storage.admission_codec import MachineKeyCommandCodec
|
|
40
49
|
from superlocalmemory.storage.admission_journal import (
|
|
41
50
|
Actor,
|
|
@@ -47,6 +56,11 @@ from superlocalmemory.storage.admission_journal import (
|
|
|
47
56
|
TerminalAdmissionError,
|
|
48
57
|
)
|
|
49
58
|
from superlocalmemory.storage.database import DatabaseManager
|
|
59
|
+
from superlocalmemory.storage.generation_fence import (
|
|
60
|
+
admitted_epoch,
|
|
61
|
+
clear_admission_epoch,
|
|
62
|
+
record_admission_epoch,
|
|
63
|
+
)
|
|
50
64
|
from superlocalmemory.storage.write_coordinator import (
|
|
51
65
|
CommandConflictError,
|
|
52
66
|
CommandKind,
|
|
@@ -64,11 +78,33 @@ Materializer = Callable[
|
|
|
64
78
|
list[str] | tuple[str, ...] | MaterializationResult,
|
|
65
79
|
]
|
|
66
80
|
|
|
81
|
+
logger = logging.getLogger("superlocalmemory.core.remember_runtime")
|
|
82
|
+
_OBLIGATION_LEDGER = ObligationLedger()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _obligation_schema_present(conn) -> bool:
|
|
86
|
+
row = conn.execute(
|
|
87
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='projection_obligations'"
|
|
88
|
+
).fetchone()
|
|
89
|
+
if row is None:
|
|
90
|
+
return False
|
|
91
|
+
columns = {r[1] for r in conn.execute("PRAGMA table_info(projection_obligations)")}
|
|
92
|
+
return "context_digest" in columns
|
|
93
|
+
|
|
67
94
|
|
|
68
95
|
class CanonicalRememberUnavailable(RuntimeError):
|
|
69
96
|
"""The daemon cannot accept a bounded canonical remember request."""
|
|
70
97
|
|
|
71
98
|
|
|
99
|
+
class DaemonAlreadyServing(RuntimeError):
|
|
100
|
+
"""A healthy SLM daemon is already serving; this instance should exit 0.
|
|
101
|
+
|
|
102
|
+
Raised by ``CanonicalRememberRuntime.start()`` (H2) when the writer claim
|
|
103
|
+
fails AND a health-verified daemon is responding on the configured port.
|
|
104
|
+
Caught by ``unified_daemon.py`` lifespan → ``sys.exit(0)``.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
|
|
72
108
|
class CanonicalMutationConflict(ValueError):
|
|
73
109
|
"""A mutation retry key was reused for different immutable input."""
|
|
74
110
|
|
|
@@ -102,6 +138,54 @@ def validate_deterministic_admission(
|
|
|
102
138
|
raise AdmissionPayloadError("content rejected by deterministic ingest policy")
|
|
103
139
|
|
|
104
140
|
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
# H2 — graceful single-instance helpers (keep all I/O in stdlib; no imports
|
|
143
|
+
# from the server layer to avoid circular dependencies with core).
|
|
144
|
+
# ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
def _get_daemon_port() -> int:
|
|
147
|
+
"""Return the HTTP port this daemon instance listens on.
|
|
148
|
+
|
|
149
|
+
Reads ``SLM_DAEMON_PORT`` from the environment (set by ``unified_daemon``
|
|
150
|
+
at startup) and falls back to the conventional default of 8765.
|
|
151
|
+
"""
|
|
152
|
+
import os
|
|
153
|
+
|
|
154
|
+
raw = os.environ.get("SLM_DAEMON_PORT", "")
|
|
155
|
+
try:
|
|
156
|
+
return int(raw)
|
|
157
|
+
except (ValueError, TypeError):
|
|
158
|
+
return 8765
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _slm_health_check(port: int) -> bool:
|
|
162
|
+
"""Return True iff a healthy SLM daemon responds on *port* within 2 s."""
|
|
163
|
+
import urllib.request
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
with urllib.request.urlopen(
|
|
167
|
+
f"http://127.0.0.1:{port}/health", timeout=2
|
|
168
|
+
) as resp:
|
|
169
|
+
return int(resp.status) == 200
|
|
170
|
+
except Exception:
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _boot_self_heal(data_dir: Path) -> None:
|
|
175
|
+
"""Run the H1 stale-artifact reaper. Fail-soft — never blocks startup."""
|
|
176
|
+
try:
|
|
177
|
+
from superlocalmemory.infra.self_heal import reap_stale_artifacts
|
|
178
|
+
|
|
179
|
+
report = reap_stale_artifacts(data_dir)
|
|
180
|
+
if report["removed"]:
|
|
181
|
+
logger.info(
|
|
182
|
+
"remember_runtime self-heal: removed %d stale artifact(s)",
|
|
183
|
+
len(report["removed"]),
|
|
184
|
+
)
|
|
185
|
+
except Exception as exc:
|
|
186
|
+
logger.debug("remember_runtime self-heal failed (non-fatal): %s", exc)
|
|
187
|
+
|
|
188
|
+
|
|
105
189
|
class _CoordinatorAdapter:
|
|
106
190
|
"""Translate the journal service's narrow protocol into typed commands."""
|
|
107
191
|
|
|
@@ -152,6 +236,7 @@ class CanonicalRememberRuntime:
|
|
|
152
236
|
self._writer = writer
|
|
153
237
|
self._materialize = materialize or _materialization_is_not_available
|
|
154
238
|
self._binding_lock = threading.RLock()
|
|
239
|
+
self._generation = 0
|
|
155
240
|
self.coordinator = WriteCoordinator(db.db_path, owner_id=owner_id)
|
|
156
241
|
self.journal = AdmissionJournal(
|
|
157
242
|
journal_path,
|
|
@@ -159,6 +244,7 @@ class CanonicalRememberRuntime:
|
|
|
159
244
|
)
|
|
160
245
|
self._service = RememberService(self.journal, _CoordinatorAdapter(self.coordinator))
|
|
161
246
|
self._started = False
|
|
247
|
+
self._obligation_schema_ok: bool | None = None
|
|
162
248
|
|
|
163
249
|
@classmethod
|
|
164
250
|
def for_engine(cls, engine: Any) -> "CanonicalRememberRuntime":
|
|
@@ -181,13 +267,72 @@ class CanonicalRememberRuntime:
|
|
|
181
267
|
)
|
|
182
268
|
|
|
183
269
|
def start(self) -> None:
|
|
184
|
-
"""Claim writer ownership, install the handler, then recover journal work.
|
|
270
|
+
"""Claim writer ownership, install the handler, then recover journal work.
|
|
271
|
+
|
|
272
|
+
H2 / G-01+G-05 — bounded graceful single-instance:
|
|
273
|
+
|
|
274
|
+
When ``claim_ownership()`` fails (another process holds the portalocker
|
|
275
|
+
flock), we enter a bounded retry loop (≤ 5 attempts, ~1 s between each,
|
|
276
|
+
total ≤ ~6 s) rather than immediately crashing or silently giving up.
|
|
277
|
+
This handles the race between a healthy daemon's HTTP-server bind and our
|
|
278
|
+
health check — the holder may be alive but not yet responding.
|
|
279
|
+
|
|
280
|
+
Per-iteration logic:
|
|
281
|
+
(a) ``claim_ownership()`` succeeds → break and proceed (holder died).
|
|
282
|
+
(b) ``_slm_health_check(port)`` is True → raise ``DaemonAlreadyServing``
|
|
283
|
+
(caught by ``unified_daemon.py`` lifespan → ``sys.exit(0)``).
|
|
284
|
+
(c) First iteration only → run ``_boot_self_heal()`` to clear
|
|
285
|
+
provably-dead metadata artifacts, then continue.
|
|
286
|
+
|
|
287
|
+
After the loop exhausts all attempts without claiming the lock, we know
|
|
288
|
+
a live process is holding the portalocker flock. Raise
|
|
289
|
+
``DaemonAlreadyServing`` (NOT ``CanonicalRememberUnavailable``) so the
|
|
290
|
+
daemon exits cleanly rather than crashing with a traceback.
|
|
291
|
+
|
|
292
|
+
INVARIANT: ``claim_ownership()`` (portalocker OS flock) is the sole
|
|
293
|
+
writer-integrity gate. We never bypass it, never signal or kill a live
|
|
294
|
+
process.
|
|
295
|
+
"""
|
|
296
|
+
import time as _time
|
|
297
|
+
|
|
185
298
|
if self._started:
|
|
186
299
|
return
|
|
187
300
|
if not self.coordinator.claim_ownership():
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
301
|
+
port = _get_daemon_port()
|
|
302
|
+
_self_heal_done = False
|
|
303
|
+
_MAX_ATTEMPTS = 5
|
|
304
|
+
|
|
305
|
+
for _attempt in range(_MAX_ATTEMPTS):
|
|
306
|
+
if _attempt > 0:
|
|
307
|
+
_time.sleep(1.0)
|
|
308
|
+
|
|
309
|
+
# (a) Re-try the claim — holder may have died in the gap.
|
|
310
|
+
if self.coordinator.claim_ownership():
|
|
311
|
+
break # claimed → proceed to handler registration
|
|
312
|
+
|
|
313
|
+
# (b) Health-verified owner → exit gracefully.
|
|
314
|
+
if _slm_health_check(port):
|
|
315
|
+
raise DaemonAlreadyServing(
|
|
316
|
+
f"another healthy SLM daemon is already serving"
|
|
317
|
+
f" on port {port}"
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
# (c) First iteration only: clear provably-dead stale artifacts.
|
|
321
|
+
if not _self_heal_done:
|
|
322
|
+
logger.info(
|
|
323
|
+
"remember_runtime: writer claim failed, no healthy"
|
|
324
|
+
" daemon on port %d; running self-heal (attempt %d/%d)",
|
|
325
|
+
port, _attempt + 1, _MAX_ATTEMPTS,
|
|
326
|
+
)
|
|
327
|
+
_boot_self_heal(self.coordinator.db_path.parent)
|
|
328
|
+
_self_heal_done = True
|
|
329
|
+
else:
|
|
330
|
+
# Loop exhausted: a live process holds the portalocker flock.
|
|
331
|
+
# Exit cleanly — never crash with a traceback.
|
|
332
|
+
raise DaemonAlreadyServing(
|
|
333
|
+
f"another SLM daemon holds the writer lock after"
|
|
334
|
+
f" {_MAX_ATTEMPTS} attempts on port {port}"
|
|
335
|
+
)
|
|
191
336
|
try:
|
|
192
337
|
self.coordinator.register_handler(CommandKind.ADMISSION, self._handle_admission)
|
|
193
338
|
self.coordinator.register_handler(CommandKind.DELETE_FACT, self._handle_mutation)
|
|
@@ -241,11 +386,13 @@ class CanonicalRememberRuntime:
|
|
|
241
386
|
self._db = db
|
|
242
387
|
self._profile_id = profile_id
|
|
243
388
|
self._writer = writer
|
|
389
|
+
self._generation += 1
|
|
244
390
|
try:
|
|
245
391
|
self.replay_pending()
|
|
246
392
|
except BaseException:
|
|
247
393
|
with self._binding_lock:
|
|
248
394
|
self._db, self._profile_id, self._writer = previous
|
|
395
|
+
self._generation -= 1
|
|
249
396
|
raise
|
|
250
397
|
|
|
251
398
|
def remember(
|
|
@@ -256,6 +403,9 @@ class CanonicalRememberRuntime:
|
|
|
256
403
|
raise CanonicalRememberUnavailable("canonical remember writer is not ready")
|
|
257
404
|
if deadline_ms < 1 or deadline_ms > 2_000:
|
|
258
405
|
raise ValueError("deadline_ms must be between 1 and 2000")
|
|
406
|
+
with self._binding_lock:
|
|
407
|
+
admitted = self._generation
|
|
408
|
+
record_admission_epoch(request.profile_id, request.idempotency_key, admitted)
|
|
259
409
|
try:
|
|
260
410
|
return self._service.remember(request, actor, deadline_ms=deadline_ms)
|
|
261
411
|
except (
|
|
@@ -266,6 +416,8 @@ class CanonicalRememberRuntime:
|
|
|
266
416
|
raise CanonicalRememberUnavailable(
|
|
267
417
|
"canonical remember is temporarily unavailable"
|
|
268
418
|
) from exc
|
|
419
|
+
finally:
|
|
420
|
+
clear_admission_epoch(request.profile_id, request.idempotency_key)
|
|
269
421
|
|
|
270
422
|
def replay_pending(self) -> int:
|
|
271
423
|
"""Finish prepared/dispatched journal entries before publishing readiness."""
|
|
@@ -442,6 +594,9 @@ class CanonicalRememberRuntime:
|
|
|
442
594
|
db = self._db
|
|
443
595
|
if request.profile_id != self._profile_id:
|
|
444
596
|
raise ValueError("admission command targets a different profile")
|
|
597
|
+
expected = admitted_epoch(request.profile_id, request.idempotency_key)
|
|
598
|
+
if expected is not None and expected != self._generation:
|
|
599
|
+
raise ValueError("admission command epoch is stale")
|
|
445
600
|
ingestion_request = IngestionRequest(
|
|
446
601
|
content=request.content,
|
|
447
602
|
profile_id=request.profile_id,
|
|
@@ -469,6 +624,7 @@ class CanonicalRememberRuntime:
|
|
|
469
624
|
receipt = command_impl.submit(ingestion_request)
|
|
470
625
|
except IngestionRejectedError as exc:
|
|
471
626
|
raise CommandRejectedError() from exc
|
|
627
|
+
self._record_projection_obligations(conn, request, receipt)
|
|
472
628
|
return WriteResult.from_receipt(
|
|
473
629
|
command,
|
|
474
630
|
{
|
|
@@ -481,6 +637,47 @@ class CanonicalRememberRuntime:
|
|
|
481
637
|
},
|
|
482
638
|
)
|
|
483
639
|
|
|
640
|
+
def _record_projection_obligations(self, conn, request, receipt) -> None:
|
|
641
|
+
"""Record per-owner APPLY obligations for the admitted facts.
|
|
642
|
+
|
|
643
|
+
Fail-closed: raises RuntimeError if M033 is absent. This is
|
|
644
|
+
intentional — a successful remember() without a corresponding
|
|
645
|
+
obligation record would make projection audits permanently incomplete.
|
|
646
|
+
Back-compat: installs without M033 must run migrations first.
|
|
647
|
+
|
|
648
|
+
Schema negative cache: ``_obligation_schema_ok`` is re-checked on
|
|
649
|
+
every call while False so that hot migrations (M033 applied while the
|
|
650
|
+
runtime is running) are detected without a restart. Once the schema
|
|
651
|
+
is confirmed present the value is cached True for the lifetime of the
|
|
652
|
+
runtime instance.
|
|
653
|
+
|
|
654
|
+
Cross-process erasure fence: distributed, multi-writer erasure fencing
|
|
655
|
+
is intentionally SCOPED OUT of V4. Single-process SQLite WAL provides
|
|
656
|
+
adequate isolation for the current deployment model. See
|
|
657
|
+
docs/architecture/erasure-fence-deferred.md for the deferred design.
|
|
658
|
+
"""
|
|
659
|
+
fact_ids = tuple(getattr(receipt, "fact_ids", ()) or ())
|
|
660
|
+
if not fact_ids:
|
|
661
|
+
return
|
|
662
|
+
# Re-check while False so a hot M033 migration is picked up without
|
|
663
|
+
# requiring a daemon restart. Cache True permanently once confirmed.
|
|
664
|
+
if not self._obligation_schema_ok:
|
|
665
|
+
self._obligation_schema_ok = _obligation_schema_present(conn)
|
|
666
|
+
if not self._obligation_schema_ok:
|
|
667
|
+
raise RuntimeError(
|
|
668
|
+
"projection_obligations schema is absent; "
|
|
669
|
+
"run migrations (M033) before ingesting facts"
|
|
670
|
+
)
|
|
671
|
+
context = OperationContext(
|
|
672
|
+
operation_id=receipt.operation_id,
|
|
673
|
+
profile_id=request.profile_id,
|
|
674
|
+
subject_id=receipt.operation_id,
|
|
675
|
+
fact_ids=fact_ids,
|
|
676
|
+
)
|
|
677
|
+
_OBLIGATION_LEDGER.record_many(
|
|
678
|
+
conn, context, REQUIRED_ADMISSION_OWNERS, ObligationKind.APPLY,
|
|
679
|
+
)
|
|
680
|
+
|
|
484
681
|
def _handle_mutation(self, conn, capability, command: WriteCommand) -> WriteResult:
|
|
485
682
|
"""Run only deterministic SQLite mutation statements under the writer."""
|
|
486
683
|
payload = command.payload
|
|
@@ -708,5 +905,6 @@ def _thaw_command_value(value: Any) -> Any:
|
|
|
708
905
|
__all__ = [
|
|
709
906
|
"CanonicalRememberRuntime",
|
|
710
907
|
"CanonicalRememberUnavailable",
|
|
908
|
+
"DaemonAlreadyServing",
|
|
711
909
|
"validate_deterministic_admission",
|
|
712
910
|
]
|
|
@@ -49,13 +49,28 @@ def is_remote_mode() -> bool:
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def mcp_stateless() -> bool:
|
|
52
|
-
"""True iff the MCP transport should run stateless
|
|
52
|
+
"""True iff the MCP Streamable-HTTP transport should run fully stateless.
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
**Default is True** (mcp 2.0.0 fully-stateless contract). Stateless mode
|
|
55
|
+
lets any gateway/hub forward ``tools/call`` without replaying the
|
|
56
|
+
``Mcp-Session-Id`` handshake (issue #39 Issue 3) and is required for
|
|
57
|
+
``Client(mode="auto")`` discover probes to stay connection-safe.
|
|
58
|
+
|
|
59
|
+
Opt-out: set ``SLM_MCP_STATEFUL=1`` to force stateful Streamable-HTTP
|
|
60
|
+
(session IDs; event store still unused unless the caller passes one).
|
|
61
|
+
``SLM_REMOTE`` / ``SLM_MCP_STATELESS`` remain recognized as positive
|
|
62
|
+
signals but are no longer required — the default is already stateless.
|
|
63
|
+
|
|
64
|
+
Application-level ``session_init`` / ``close_session`` are orthogonal
|
|
65
|
+
(session_id is a string parameter persisted in the memories table).
|
|
57
66
|
"""
|
|
58
|
-
|
|
67
|
+
if _is_truthy(os.environ.get("SLM_MCP_STATEFUL")):
|
|
68
|
+
return False
|
|
69
|
+
# Explicit SLM_MCP_STATELESS=0/false still honoured as opt-out when set.
|
|
70
|
+
raw = os.environ.get("SLM_MCP_STATELESS")
|
|
71
|
+
if raw is not None and raw.strip() != "":
|
|
72
|
+
return _is_truthy(raw)
|
|
73
|
+
return True
|
|
59
74
|
|
|
60
75
|
|
|
61
76
|
def _allowlist_entries() -> list[str]:
|
|
@@ -22,6 +22,7 @@ if TYPE_CHECKING:
|
|
|
22
22
|
from superlocalmemory.core.hooks import HookRegistry
|
|
23
23
|
from superlocalmemory.storage.database import DatabaseManager
|
|
24
24
|
|
|
25
|
+
from superlocalmemory.storage.erasure_fence import is_erasing
|
|
25
26
|
from superlocalmemory.storage.models import (
|
|
26
27
|
AtomicFact,
|
|
27
28
|
FactType,
|
|
@@ -271,6 +272,111 @@ def _upsert_fact_vectors(fact, profile_id, ann_index, vector_store, embedder=Non
|
|
|
271
272
|
)
|
|
272
273
|
|
|
273
274
|
|
|
275
|
+
class _TombstoneReadError(Exception):
|
|
276
|
+
pass
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _fact_is_tombstoned(db: DatabaseManager, profile_id: str, fact_id: str) -> bool:
|
|
280
|
+
try:
|
|
281
|
+
rows = db.execute(
|
|
282
|
+
"SELECT 1 FROM projection_tombstones "
|
|
283
|
+
"WHERE profile_id = ? AND fact_id = ? LIMIT 1",
|
|
284
|
+
(profile_id, fact_id),
|
|
285
|
+
)
|
|
286
|
+
return bool(rows)
|
|
287
|
+
except Exception as exc:
|
|
288
|
+
raise _TombstoneReadError(
|
|
289
|
+
f"tombstone check failed for {fact_id[:16]}: {type(exc).__name__}: {exc}"
|
|
290
|
+
) from exc
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _residue_table_exists(db: DatabaseManager, name: str) -> bool:
|
|
294
|
+
try:
|
|
295
|
+
return bool(db.execute(
|
|
296
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name = ? LIMIT 1",
|
|
297
|
+
(name,),
|
|
298
|
+
))
|
|
299
|
+
except Exception:
|
|
300
|
+
# Cannot determine existence: treat as present so the residue probe runs
|
|
301
|
+
# and fails closed rather than silently reporting "no residue".
|
|
302
|
+
return True
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _read_tombstoned_with_retry(
|
|
306
|
+
db: DatabaseManager, profile_id: str, fact_id: str, attempts: int = 3,
|
|
307
|
+
) -> bool:
|
|
308
|
+
last: _TombstoneReadError | None = None
|
|
309
|
+
for _ in range(max(1, attempts)):
|
|
310
|
+
try:
|
|
311
|
+
return _fact_is_tombstoned(db, profile_id, fact_id)
|
|
312
|
+
except _TombstoneReadError as exc:
|
|
313
|
+
last = exc
|
|
314
|
+
raise last if last is not None else _TombstoneReadError("tombstone read failed")
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _vector_residue_present(db: DatabaseManager, fact_id: str) -> bool:
|
|
318
|
+
uncertain = False
|
|
319
|
+
for table in ("embedding_metadata", "vector_row_map"):
|
|
320
|
+
if not _residue_table_exists(db, table):
|
|
321
|
+
continue
|
|
322
|
+
try:
|
|
323
|
+
if db.execute(
|
|
324
|
+
f"SELECT 1 FROM {table} WHERE fact_id = ? LIMIT 1", (fact_id,)
|
|
325
|
+
):
|
|
326
|
+
return True
|
|
327
|
+
except Exception:
|
|
328
|
+
uncertain = True
|
|
329
|
+
return uncertain
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _drop_resurrected_facts(
|
|
333
|
+
db: DatabaseManager,
|
|
334
|
+
profile_id: str,
|
|
335
|
+
stored_ids: list[str],
|
|
336
|
+
vector_store: Any,
|
|
337
|
+
ann_index: Any,
|
|
338
|
+
retrieval_engine: Any,
|
|
339
|
+
) -> list[str]:
|
|
340
|
+
survivors: list[str] = []
|
|
341
|
+
for fid in stored_ids:
|
|
342
|
+
try:
|
|
343
|
+
tombstoned = _read_tombstoned_with_retry(db, profile_id, fid)
|
|
344
|
+
except _TombstoneReadError as exc:
|
|
345
|
+
if is_erasing(profile_id, fid):
|
|
346
|
+
# Tombstone unreadable but the in-process fence confirms an
|
|
347
|
+
# erasure is in flight — clean up rather than leave residue.
|
|
348
|
+
tombstoned = True
|
|
349
|
+
else:
|
|
350
|
+
logger.error(
|
|
351
|
+
"Tombstone read error in drop_resurrected for %s, deferring: %s",
|
|
352
|
+
fid[:16], exc,
|
|
353
|
+
)
|
|
354
|
+
continue
|
|
355
|
+
if not tombstoned:
|
|
356
|
+
survivors.append(fid)
|
|
357
|
+
continue
|
|
358
|
+
vec_store_ok = vector_store is not None and getattr(vector_store, "available", False)
|
|
359
|
+
try:
|
|
360
|
+
if vec_store_ok:
|
|
361
|
+
vector_store.delete(fid)
|
|
362
|
+
if ann_index is not None and hasattr(ann_index, "remove"):
|
|
363
|
+
ann_index.remove(fid)
|
|
364
|
+
bm25 = getattr(retrieval_engine, "_bm25", None) if retrieval_engine else None
|
|
365
|
+
if bm25 is not None and hasattr(bm25, "remove_fact"):
|
|
366
|
+
bm25.remove_fact(fid)
|
|
367
|
+
db.delete_bm25_tokens_for_fact(fid)
|
|
368
|
+
db.delete_fact(fid, profile_id=profile_id)
|
|
369
|
+
except Exception as exc:
|
|
370
|
+
logger.warning("resurrection undo failed for %s: %s", fid[:16], exc)
|
|
371
|
+
if _vector_residue_present(db, fid):
|
|
372
|
+
logger.error(
|
|
373
|
+
"resurrection undo incomplete for %s: vector residue remains "
|
|
374
|
+
"(vec_store_available=%s); tombstone preserved",
|
|
375
|
+
fid[:16], vec_store_ok,
|
|
376
|
+
)
|
|
377
|
+
return survivors
|
|
378
|
+
|
|
379
|
+
|
|
274
380
|
# ---------------------------------------------------------------------------
|
|
275
381
|
# run_store (was MemoryEngine.store)
|
|
276
382
|
# ---------------------------------------------------------------------------
|
|
@@ -533,6 +639,17 @@ def run_store(
|
|
|
533
639
|
|
|
534
640
|
stored_ids: list[str] = []
|
|
535
641
|
for fact in facts:
|
|
642
|
+
try:
|
|
643
|
+
if _fact_is_tombstoned(db, profile_id, fact.fact_id):
|
|
644
|
+
continue
|
|
645
|
+
except _TombstoneReadError as _tse:
|
|
646
|
+
logger.error(
|
|
647
|
+
"Tombstone read error for %s, deferring ingestion: %s",
|
|
648
|
+
fact.fact_id[:16], _tse,
|
|
649
|
+
)
|
|
650
|
+
continue
|
|
651
|
+
if is_erasing(profile_id, fact.fact_id):
|
|
652
|
+
continue
|
|
536
653
|
fact = enrich_fact(
|
|
537
654
|
fact, record, profile_id,
|
|
538
655
|
embedder=embedder,
|
|
@@ -852,6 +969,10 @@ def run_store(
|
|
|
852
969
|
except Exception:
|
|
853
970
|
provenance_complete = False
|
|
854
971
|
|
|
972
|
+
stored_ids = _drop_resurrected_facts(
|
|
973
|
+
db, profile_id, stored_ids, vector_store, ann_index, retrieval_engine,
|
|
974
|
+
)
|
|
975
|
+
|
|
855
976
|
logger.info("Stored %d facts (session=%s)", len(stored_ids), session_id)
|
|
856
977
|
|
|
857
978
|
if derivation_report is not None:
|
|
@@ -981,6 +1102,25 @@ def run_store_fact_direct(
|
|
|
981
1102
|
# run_close_session (was MemoryEngine.close_session)
|
|
982
1103
|
# ---------------------------------------------------------------------------
|
|
983
1104
|
|
|
1105
|
+
def _session_already_summarised(
|
|
1106
|
+
db: DatabaseManager,
|
|
1107
|
+
profile_id: str,
|
|
1108
|
+
session_id: str,
|
|
1109
|
+
) -> bool:
|
|
1110
|
+
"""True if close_session already wrote temporal summaries for this session."""
|
|
1111
|
+
if not session_id:
|
|
1112
|
+
return False
|
|
1113
|
+
try:
|
|
1114
|
+
rows = db.execute(
|
|
1115
|
+
"SELECT 1 FROM temporal_events "
|
|
1116
|
+
"WHERE profile_id = ? AND description LIKE ? LIMIT 1",
|
|
1117
|
+
(profile_id, f"Session {session_id}:%"),
|
|
1118
|
+
)
|
|
1119
|
+
return bool(rows)
|
|
1120
|
+
except Exception:
|
|
1121
|
+
return False
|
|
1122
|
+
|
|
1123
|
+
|
|
984
1124
|
def run_close_session(
|
|
985
1125
|
session_id: str,
|
|
986
1126
|
profile_id: str,
|
|
@@ -993,10 +1133,20 @@ def run_close_session(
|
|
|
993
1133
|
with session scope. Enables temporal queries like "What happened
|
|
994
1134
|
in session 3?"
|
|
995
1135
|
|
|
1136
|
+
Idempotent: if temporal summaries for this session already exist,
|
|
1137
|
+
returns 0 without writing duplicates.
|
|
1138
|
+
|
|
996
1139
|
Returns number of session summary events created.
|
|
997
1140
|
"""
|
|
998
1141
|
from superlocalmemory.storage.models import TemporalEvent
|
|
999
1142
|
|
|
1143
|
+
if not session_id:
|
|
1144
|
+
return 0
|
|
1145
|
+
|
|
1146
|
+
if _session_already_summarised(db, profile_id, session_id):
|
|
1147
|
+
logger.debug("Session %s already summarised — skip close", session_id)
|
|
1148
|
+
return 0
|
|
1149
|
+
|
|
1000
1150
|
facts = db.get_all_facts(profile_id)
|
|
1001
1151
|
session_facts = [f for f in facts if f.session_id == session_id]
|
|
1002
1152
|
if not session_facts:
|
|
@@ -119,6 +119,25 @@ def compute_topic_signature(
|
|
|
119
119
|
words = _WORD.findall(lowered)
|
|
120
120
|
content_words = [w for w in words if w not in _STOPWORDS and len(w) >= 3]
|
|
121
121
|
|
|
122
|
+
# LLD-13 Track C.1 — merge entity names into content_words before
|
|
123
|
+
# computing bigrams. An entity ID of the form "category-name" (e.g.
|
|
124
|
+
# "entity-qualixar") contributes its *name* part ("qualixar"); an ID
|
|
125
|
+
# without a hyphen (e.g. "e001") contributes the whole ID. Words
|
|
126
|
+
# already present in content_words are silently deduplicated, so a
|
|
127
|
+
# prewarm caller and the UserPromptSubmit hook produce byte-identical
|
|
128
|
+
# signatures when the entity is already named in the prompt text.
|
|
129
|
+
# Backward-compatible: when entity_hits is empty/None the output is
|
|
130
|
+
# unchanged from the pre-LLD-13 signature.
|
|
131
|
+
if entity_hits:
|
|
132
|
+
_seen = set(content_words)
|
|
133
|
+
for _eid in entity_hits:
|
|
134
|
+
_eid_str = str(_eid)
|
|
135
|
+
_name = _eid_str.split("-", 1)[1] if "-" in _eid_str else _eid_str
|
|
136
|
+
_name_lc = _name.lower()
|
|
137
|
+
if _name_lc not in _seen and len(_name_lc) >= 3:
|
|
138
|
+
content_words.append(_name_lc)
|
|
139
|
+
_seen.add(_name_lc)
|
|
140
|
+
|
|
122
141
|
# 5. Bigrams from the ORIGINAL token stream order. Preserves "foo bar"
|
|
123
142
|
# vs "bar foo" distinction and resists stopword-only differentiation.
|
|
124
143
|
bigrams = [f"{a}_{b}" for a, b in zip(content_words, content_words[1:])]
|
|
@@ -132,10 +151,6 @@ def compute_topic_signature(
|
|
|
132
151
|
_canon(content_words),
|
|
133
152
|
_canon(bigrams),
|
|
134
153
|
]
|
|
135
|
-
# LLD-13: append entity-hits group ONLY when non-empty. Empty/missing
|
|
136
|
-
# preserves the byte-identical v3.4.22 pre-Living-Brain signature.
|
|
137
|
-
if entity_hits:
|
|
138
|
-
groups.append(_canon([str(e) for e in entity_hits]))
|
|
139
154
|
material = "\0\0".join(groups)
|
|
140
155
|
return hashlib.sha256(material.encode("utf-8")).hexdigest()[:_SIG_LEN]
|
|
141
156
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from superlocalmemory.core.transactions.erasure import (
|
|
7
|
+
ErasureProofRecord,
|
|
8
|
+
ErasureReceipt,
|
|
9
|
+
ErasureService,
|
|
10
|
+
ErasureState,
|
|
11
|
+
RemoveResult,
|
|
12
|
+
compute_erasure_hash,
|
|
13
|
+
fetch_receipt,
|
|
14
|
+
is_tombstoned,
|
|
15
|
+
tombstone_memory_id,
|
|
16
|
+
verify_receipt,
|
|
17
|
+
write_tombstones,
|
|
18
|
+
)
|
|
19
|
+
from superlocalmemory.core.transactions.manifest import (
|
|
20
|
+
CompletionManifest,
|
|
21
|
+
ManifestState,
|
|
22
|
+
OwnerEvidence,
|
|
23
|
+
build_evidence,
|
|
24
|
+
compute_envelope_hash,
|
|
25
|
+
derive_state,
|
|
26
|
+
hash_envelope_fields,
|
|
27
|
+
)
|
|
28
|
+
from superlocalmemory.core.transactions.obligations import (
|
|
29
|
+
Obligation,
|
|
30
|
+
ObligationConflictError,
|
|
31
|
+
ObligationLedger,
|
|
32
|
+
)
|
|
33
|
+
from superlocalmemory.core.transactions.owners import (
|
|
34
|
+
ObligationKind,
|
|
35
|
+
ObligationState,
|
|
36
|
+
OperationContext,
|
|
37
|
+
OwnerErasureProof,
|
|
38
|
+
OwnerHealth,
|
|
39
|
+
OwnerResult,
|
|
40
|
+
ProjectionOwner,
|
|
41
|
+
is_terminal_success,
|
|
42
|
+
)
|
|
43
|
+
from superlocalmemory.core.transactions.reconciler import Reconciler
|
|
44
|
+
from superlocalmemory.core.transactions.service import MemoryTransactionService
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"CompletionManifest",
|
|
48
|
+
"ErasureProofRecord",
|
|
49
|
+
"ErasureReceipt",
|
|
50
|
+
"ErasureService",
|
|
51
|
+
"ErasureState",
|
|
52
|
+
"ManifestState",
|
|
53
|
+
"MemoryTransactionService",
|
|
54
|
+
"Obligation",
|
|
55
|
+
"ObligationConflictError",
|
|
56
|
+
"ObligationKind",
|
|
57
|
+
"ObligationLedger",
|
|
58
|
+
"ObligationState",
|
|
59
|
+
"OperationContext",
|
|
60
|
+
"OwnerErasureProof",
|
|
61
|
+
"OwnerEvidence",
|
|
62
|
+
"OwnerHealth",
|
|
63
|
+
"OwnerResult",
|
|
64
|
+
"ProjectionOwner",
|
|
65
|
+
"Reconciler",
|
|
66
|
+
"RemoveResult",
|
|
67
|
+
"build_evidence",
|
|
68
|
+
"compute_envelope_hash",
|
|
69
|
+
"compute_erasure_hash",
|
|
70
|
+
"derive_state",
|
|
71
|
+
"fetch_receipt",
|
|
72
|
+
"hash_envelope_fields",
|
|
73
|
+
"is_terminal_success",
|
|
74
|
+
"is_tombstoned",
|
|
75
|
+
"tombstone_memory_id",
|
|
76
|
+
"verify_receipt",
|
|
77
|
+
"write_tombstones",
|
|
78
|
+
]
|