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.
Files changed (212) hide show
  1. package/ATTRIBUTION.md +4 -4
  2. package/CHANGELOG.md +113 -121
  3. package/README.md +65 -63
  4. package/docs/pi-dev-integration.md +1 -1
  5. package/package.json +6 -1
  6. package/plugin/.claude-plugin/plugin.json +1 -1
  7. package/plugin/CLAUDE.md +3 -3
  8. package/plugin/agents/slm-governance-advisor.md +1 -1
  9. package/plugin/agents/slm-loop-runner.md +1 -1
  10. package/plugin/agents/slm-memory-advisor.md +1 -1
  11. package/plugin/agents/slm-optimize-advisor.md +1 -1
  12. package/plugin/requirements.txt +1 -1
  13. package/plugin/skills/slm-cache/SKILL.md +1 -1
  14. package/plugin/skills/slm-compress/SKILL.md +1 -1
  15. package/plugin/skills/slm-governance/SKILL.md +1 -1
  16. package/plugin/skills/slm-graph/SKILL.md +1 -1
  17. package/plugin/skills/slm-loop/SKILL.md +1 -1
  18. package/plugin/skills/slm-mesh/SKILL.md +1 -1
  19. package/plugin/skills/slm-profile/SKILL.md +1 -1
  20. package/plugin/skills/slm-recall/SKILL.md +1 -1
  21. package/plugin/skills/slm-remember/SKILL.md +1 -1
  22. package/plugin/skills/slm-scope/SKILL.md +1 -1
  23. package/plugin/skills/slm-session/SKILL.md +1 -1
  24. package/plugin/skills/slm-status/SKILL.md +1 -1
  25. package/plugin-src/rules/AGENTS.md +1 -1
  26. package/plugin-src/skills/slm-cache/SKILL.md +1 -1
  27. package/plugin-src/skills/slm-compress/SKILL.md +1 -1
  28. package/plugin-src/skills/slm-governance/SKILL.md +248 -0
  29. package/plugin-src/skills/slm-graph/SKILL.md +1 -1
  30. package/plugin-src/skills/slm-loop/SKILL.md +99 -0
  31. package/plugin-src/skills/slm-mesh/SKILL.md +282 -0
  32. package/plugin-src/skills/slm-profile/SKILL.md +148 -0
  33. package/plugin-src/skills/slm-recall/SKILL.md +1 -1
  34. package/plugin-src/skills/slm-remember/SKILL.md +1 -1
  35. package/plugin-src/skills/slm-scope/SKILL.md +176 -0
  36. package/plugin-src/skills/slm-session/SKILL.md +1 -1
  37. package/plugin-src/skills/slm-status/SKILL.md +1 -1
  38. package/pyproject.toml +11 -4
  39. package/src/superlocalmemory/__init__.py +1 -1
  40. package/src/superlocalmemory/cli/commands.py +125 -11
  41. package/src/superlocalmemory/cli/daemon.py +5 -1
  42. package/src/superlocalmemory/cli/main.py +35 -2
  43. package/src/superlocalmemory/cli/ops_cmd.py +281 -0
  44. package/src/superlocalmemory/cli/setup_wizard.py +1 -1
  45. package/src/superlocalmemory/compliance/audit.py +65 -0
  46. package/src/superlocalmemory/compliance/eu_ai_act.py +27 -57
  47. package/src/superlocalmemory/compliance/gdpr.py +416 -20
  48. package/src/superlocalmemory/compliance/retention.py +74 -22
  49. package/src/superlocalmemory/compliance/scheduler.py +78 -9
  50. package/src/superlocalmemory/core/actor_context.py +166 -0
  51. package/src/superlocalmemory/core/admission.py +549 -0
  52. package/src/superlocalmemory/core/backend_orchestrator.py +23 -10
  53. package/src/superlocalmemory/core/config.py +202 -24
  54. package/src/superlocalmemory/core/consolidation_engine.py +13 -13
  55. package/src/superlocalmemory/core/context_cache.py +28 -0
  56. package/src/superlocalmemory/core/embeddings.py +64 -2
  57. package/src/superlocalmemory/core/engine.py +7 -2
  58. package/src/superlocalmemory/core/engine_ingestion.py +65 -3
  59. package/src/superlocalmemory/core/engine_wiring.py +36 -9
  60. package/src/superlocalmemory/core/ingest_policy.py +38 -0
  61. package/src/superlocalmemory/core/maintenance.py +255 -0
  62. package/src/superlocalmemory/core/modes.py +40 -13
  63. package/src/superlocalmemory/core/mutations.py +437 -44
  64. package/src/superlocalmemory/core/operation_policy.py +92 -0
  65. package/src/superlocalmemory/core/operation_policy_registry.py +542 -0
  66. package/src/superlocalmemory/core/operation_request.py +127 -0
  67. package/src/superlocalmemory/core/ops_remediation.py +542 -0
  68. package/src/superlocalmemory/core/recall_pipeline.py +7 -0
  69. package/src/superlocalmemory/core/remember_runtime.py +202 -4
  70. package/src/superlocalmemory/core/remote_mode.py +20 -5
  71. package/src/superlocalmemory/core/store_pipeline.py +150 -0
  72. package/src/superlocalmemory/core/topic_signature.py +19 -4
  73. package/src/superlocalmemory/core/transactions/__init__.py +78 -0
  74. package/src/superlocalmemory/core/transactions/concrete_owners.py +597 -0
  75. package/src/superlocalmemory/core/transactions/erasure.py +825 -0
  76. package/src/superlocalmemory/core/transactions/manifest.py +255 -0
  77. package/src/superlocalmemory/core/transactions/manifest_key.py +155 -0
  78. package/src/superlocalmemory/core/transactions/obligations.py +272 -0
  79. package/src/superlocalmemory/core/transactions/owners.py +114 -0
  80. package/src/superlocalmemory/core/transactions/reconciler.py +285 -0
  81. package/src/superlocalmemory/core/transactions/service.py +330 -0
  82. package/src/superlocalmemory/core/worker_pool.py +33 -5
  83. package/src/superlocalmemory/encoding/cognitive_consolidator.py +70 -28
  84. package/src/superlocalmemory/encoding/emotional.py +75 -14
  85. package/src/superlocalmemory/encoding/scene_builder.py +115 -13
  86. package/src/superlocalmemory/encoding/temporal_parser.py +4 -0
  87. package/src/superlocalmemory/evolution/blind_verifier.py +11 -4
  88. package/src/superlocalmemory/evolution/evolution_store.py +244 -4
  89. package/src/superlocalmemory/evolution/llm_dispatch.py +40 -0
  90. package/src/superlocalmemory/evolution/model_selection.py +18 -3
  91. package/src/superlocalmemory/evolution/mutation_generator.py +3 -0
  92. package/src/superlocalmemory/evolution/skill_activator.py +270 -0
  93. package/src/superlocalmemory/evolution/skill_evolver.py +281 -59
  94. package/src/superlocalmemory/evolution/types.py +30 -8
  95. package/src/superlocalmemory/graph/cozo_backend.py +17 -9
  96. package/src/superlocalmemory/hooks/auto_invoker.py +2 -1
  97. package/src/superlocalmemory/hooks/auto_recall.py +64 -30
  98. package/src/superlocalmemory/hooks/codex_assets.py +14 -1
  99. package/src/superlocalmemory/infra/backup.py +434 -7
  100. package/src/superlocalmemory/infra/process_reaper.py +18 -0
  101. package/src/superlocalmemory/infra/self_heal.py +401 -0
  102. package/src/superlocalmemory/learning/feedback.py +52 -9
  103. package/src/superlocalmemory/loops/engine.py +10 -0
  104. package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
  105. package/src/superlocalmemory/mcp/http_transport.py +30 -331
  106. package/src/superlocalmemory/mcp/profiles.py +5 -0
  107. package/src/superlocalmemory/mcp/resources.py +8 -0
  108. package/src/superlocalmemory/mcp/server.py +51 -4
  109. package/src/superlocalmemory/mcp/shared.py +19 -0
  110. package/src/superlocalmemory/mcp/tools_active.py +25 -4
  111. package/src/superlocalmemory/mcp/tools_code_graph.py +26 -18
  112. package/src/superlocalmemory/mcp/tools_context.py +50 -8
  113. package/src/superlocalmemory/mcp/tools_core.py +69 -21
  114. package/src/superlocalmemory/mcp/tools_evolution.py +9 -2
  115. package/src/superlocalmemory/mcp/tools_learning.py +21 -10
  116. package/src/superlocalmemory/mcp/tools_loops.py +29 -18
  117. package/src/superlocalmemory/mcp/tools_mesh.py +8 -0
  118. package/src/superlocalmemory/mcp/tools_ops.py +115 -0
  119. package/src/superlocalmemory/mcp/tools_optimize.py +4 -0
  120. package/src/superlocalmemory/mcp/tools_v28.py +10 -3
  121. package/src/superlocalmemory/mcp/tools_v3.py +34 -14
  122. package/src/superlocalmemory/mcp/tools_v33.py +18 -33
  123. package/src/superlocalmemory/mesh/broker.py +124 -46
  124. package/src/superlocalmemory/mesh/broker_security.py +470 -0
  125. package/src/superlocalmemory/mesh/discovery.py +365 -0
  126. package/src/superlocalmemory/mesh/lock_protocol.py +313 -0
  127. package/src/superlocalmemory/mesh/node_identity.py +97 -0
  128. package/src/superlocalmemory/mesh/outbox_remote.py +429 -0
  129. package/src/superlocalmemory/mesh/remote_sync.py +511 -28
  130. package/src/superlocalmemory/mesh/state_sync.py +286 -0
  131. package/src/superlocalmemory/optimize/config/store.py +45 -0
  132. package/src/superlocalmemory/parameterization/cross_project.py +12 -0
  133. package/src/superlocalmemory/parameterization/prompt_injector.py +13 -11
  134. package/src/superlocalmemory/parameterization/prompt_lifecycle.py +8 -2
  135. package/src/superlocalmemory/parameterization/workflow_miner.py +17 -0
  136. package/src/superlocalmemory/retrieval/ann_index.py +5 -0
  137. package/src/superlocalmemory/retrieval/bm25_channel.py +49 -2
  138. package/src/superlocalmemory/retrieval/engine.py +19 -4
  139. package/src/superlocalmemory/retrieval/fusion.py +4 -1
  140. package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -3
  141. package/src/superlocalmemory/retrieval/remote_reranker.py +47 -22
  142. package/src/superlocalmemory/retrieval/reranker.py +32 -1
  143. package/src/superlocalmemory/retrieval/temporal_channel.py +16 -3
  144. package/src/superlocalmemory/retrieval/temporal_utils.py +107 -0
  145. package/src/superlocalmemory/retrieval/temporal_validity_filter.py +155 -42
  146. package/src/superlocalmemory/retrieval/vector_store.py +214 -8
  147. package/src/superlocalmemory/server/api.py +5 -5
  148. package/src/superlocalmemory/server/egress_policy.py +258 -0
  149. package/src/superlocalmemory/server/rbac_enforce.py +32 -0
  150. package/src/superlocalmemory/server/route_mutations.py +20 -0
  151. package/src/superlocalmemory/server/routes/compliance.py +153 -7
  152. package/src/superlocalmemory/server/routes/data_io.py +43 -2
  153. package/src/superlocalmemory/server/routes/events.py +15 -0
  154. package/src/superlocalmemory/server/routes/memories.py +56 -3
  155. package/src/superlocalmemory/server/routes/mesh.py +82 -1
  156. package/src/superlocalmemory/server/routes/mesh_lock.py +54 -0
  157. package/src/superlocalmemory/server/routes/mesh_state.py +63 -0
  158. package/src/superlocalmemory/server/routes/v3_api.py +50 -27
  159. package/src/superlocalmemory/server/routes/ws.py +86 -0
  160. package/src/superlocalmemory/server/ui.py +6 -6
  161. package/src/superlocalmemory/server/unified_daemon.py +942 -119
  162. package/src/superlocalmemory/storage/_migration_internals.py +568 -0
  163. package/src/superlocalmemory/storage/_schema_version.py +110 -0
  164. package/src/superlocalmemory/storage/database.py +329 -24
  165. package/src/superlocalmemory/storage/embedding_migrator.py +246 -51
  166. package/src/superlocalmemory/storage/erasure_fence.py +45 -0
  167. package/src/superlocalmemory/storage/generation_fence.py +63 -0
  168. package/src/superlocalmemory/storage/migration_runner.py +140 -417
  169. package/src/superlocalmemory/storage/migrations/M009_model_lineage.py +40 -0
  170. package/src/superlocalmemory/storage/migrations/M033_projection_transactions.py +148 -0
  171. package/src/superlocalmemory/storage/migrations/M034_obligation_integrity.py +58 -0
  172. package/src/superlocalmemory/storage/migrations/M035_erasure_receipts.py +113 -0
  173. package/src/superlocalmemory/storage/migrations/M036_vector_row_map.py +107 -0
  174. package/src/superlocalmemory/storage/migrations/M037_manifest_hmac_version.py +162 -0
  175. package/src/superlocalmemory/storage/migrations/{M033_learning_feedback_channel.py → M038_learning_feedback_channel.py} +3 -3
  176. package/src/superlocalmemory/storage/migrations/M039_scene_fact_members.py +137 -0
  177. package/src/superlocalmemory/storage/migrations/__init__.py +4 -2
  178. package/src/superlocalmemory/storage/schema.py +67 -0
  179. package/src/superlocalmemory/storage/write_coordinator.py +125 -0
  180. package/src/superlocalmemory/trust/scorer.py +28 -4
  181. package/src/superlocalmemory/ui/index.html +14 -3
  182. package/src/superlocalmemory/ui/js/auto-settings.js +12 -1
  183. package/src/superlocalmemory/ui/js/brain.js +6 -4
  184. package/src/superlocalmemory/ui/js/compliance.js +66 -12
  185. package/src/superlocalmemory/ui/js/dashboard.js +13 -3
  186. package/src/superlocalmemory/ui/js/feedback.js +8 -2
  187. package/src/superlocalmemory/ui/js/lifecycle.js +7 -1
  188. package/src/superlocalmemory/ui/js/modal.js +272 -5
  189. package/src/superlocalmemory/ui/js/od-backup.js +9 -2
  190. package/src/superlocalmemory/ui/js/od-compliance-ext.js +301 -0
  191. package/src/superlocalmemory/ui/js/od-operations.js +154 -23
  192. package/src/superlocalmemory/ui/js/od-ops-health.js +417 -0
  193. package/src/superlocalmemory/ui/js/od-optimize.js +35 -21
  194. package/src/superlocalmemory/ui/js/od-team.js +9 -2
  195. package/src/superlocalmemory/ui/js/optimize.js +13 -16
  196. package/src/superlocalmemory/ui/js/profiles.js +7 -3
  197. package/src/superlocalmemory/ui/js/settings.js +7 -1
  198. package/src/superlocalmemory/vector/lancedb_backend.py +19 -9
  199. package/src/superlocalmemory/attribution/mathematical_dna.py +0 -235
  200. package/src/superlocalmemory/cli/post_install.py +0 -114
  201. package/src/superlocalmemory/core/clock_monitor.py +0 -45
  202. package/src/superlocalmemory/core/db_pool.py +0 -80
  203. package/src/superlocalmemory/core/error_catalog.py +0 -113
  204. package/src/superlocalmemory/core/loop_watchdog.py +0 -56
  205. package/src/superlocalmemory/core/priority_queue.py +0 -61
  206. package/src/superlocalmemory/core/pruning_engine.py +0 -216
  207. package/src/superlocalmemory/core/queue_dispatcher.py +0 -73
  208. package/src/superlocalmemory/core/slmignore.py +0 -125
  209. package/src/superlocalmemory/infra/heartbeat_monitor.py +0 -140
  210. package/src/superlocalmemory/infra/webhook_dispatcher.py +0 -247
  211. package/src/superlocalmemory/learning/quantization_scheduler.py +0 -320
  212. package/src/superlocalmemory/storage/access_control.py +0 -182
@@ -26,6 +26,46 @@ _REQUIRED_COLS = frozenset({
26
26
  "shadow_results_json", "promoted_at", "rollback_reason",
27
27
  })
28
28
 
29
+ # Column definitions in application order: name → SQLite type fragment.
30
+ _COLUMN_ADDITIONS: tuple[tuple[str, str], ...] = (
31
+ ("is_previous", "INTEGER DEFAULT 0"),
32
+ ("is_rollback", "INTEGER DEFAULT 0"),
33
+ ("is_candidate", "INTEGER DEFAULT 0"),
34
+ ("shadow_results_json", "TEXT"),
35
+ ("promoted_at", "TEXT"),
36
+ ("rollback_reason", "TEXT"),
37
+ )
38
+
39
+
40
+ def apply(conn: sqlite3.Connection) -> None:
41
+ """Idempotent, column-by-column ALTER for crash-safe recovery.
42
+
43
+ Each column is only added when absent, so any committed prefix from a
44
+ prior interrupted run is handled transparently. Indexes use the standard
45
+ IF NOT EXISTS guard. Callers must not call commit/rollback — the runner
46
+ owns the transaction boundary.
47
+ """
48
+ existing_cols: set[str] = {
49
+ r[1]
50
+ for r in conn.execute(
51
+ "PRAGMA table_info(learning_model_state)"
52
+ ).fetchall()
53
+ }
54
+ for col_name, col_def in _COLUMN_ADDITIONS:
55
+ if col_name not in existing_cols:
56
+ conn.execute(
57
+ f"ALTER TABLE learning_model_state ADD COLUMN {col_name} {col_def}"
58
+ )
59
+ # Partial-index creation: IF NOT EXISTS guards idempotency.
60
+ conn.execute(
61
+ "CREATE UNIQUE INDEX IF NOT EXISTS idx_model_active_one "
62
+ "ON learning_model_state(profile_id) WHERE is_active=1"
63
+ )
64
+ conn.execute(
65
+ "CREATE UNIQUE INDEX IF NOT EXISTS idx_model_candidate_one "
66
+ "ON learning_model_state(profile_id) WHERE is_candidate=1"
67
+ )
68
+
29
69
 
30
70
  def verify(conn: sqlite3.Connection) -> bool:
31
71
  try:
@@ -0,0 +1,148 @@
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
+ import sqlite3
7
+
8
+ NAME = "M033_projection_transactions"
9
+ DB_TARGET = "memory"
10
+
11
+ DDL = """
12
+ CREATE TABLE IF NOT EXISTS projection_obligations (
13
+ obligation_id INTEGER PRIMARY KEY AUTOINCREMENT,
14
+ operation_id TEXT NOT NULL,
15
+ profile_id TEXT NOT NULL,
16
+ owner TEXT NOT NULL,
17
+ kind TEXT NOT NULL CHECK (kind IN ('apply', 'erase')),
18
+ subject_id TEXT NOT NULL,
19
+ revision INTEGER NOT NULL DEFAULT 0,
20
+ state TEXT NOT NULL DEFAULT 'pending' CHECK (
21
+ state IN (
22
+ 'pending', 'applied', 'verified',
23
+ 'failed', 'compensated', 'erased'
24
+ )
25
+ ),
26
+ checksum TEXT,
27
+ detail TEXT,
28
+ attempts INTEGER NOT NULL DEFAULT 0,
29
+ created_at REAL NOT NULL,
30
+ updated_at REAL NOT NULL,
31
+ UNIQUE (operation_id, owner, kind)
32
+ );
33
+ CREATE INDEX IF NOT EXISTS idx_projection_obligations_operation
34
+ ON projection_obligations (operation_id);
35
+ CREATE INDEX IF NOT EXISTS idx_projection_obligations_profile_state
36
+ ON projection_obligations (profile_id, state);
37
+ CREATE INDEX IF NOT EXISTS idx_projection_obligations_state_updated
38
+ ON projection_obligations (state, updated_at);
39
+ CREATE TABLE IF NOT EXISTS completion_manifests (
40
+ operation_id TEXT PRIMARY KEY,
41
+ profile_id TEXT NOT NULL,
42
+ state TEXT NOT NULL CHECK (
43
+ state IN ('COMPLETE', 'DEGRADED', 'FAILED')
44
+ ),
45
+ all_met INTEGER NOT NULL DEFAULT 0 CHECK (all_met IN (0, 1)),
46
+ obligation_count INTEGER NOT NULL DEFAULT 0,
47
+ owner_evidence_json TEXT NOT NULL,
48
+ manifest_hash TEXT NOT NULL,
49
+ created_at REAL NOT NULL,
50
+ updated_at REAL NOT NULL
51
+ );
52
+ CREATE INDEX IF NOT EXISTS idx_completion_manifests_profile_state
53
+ ON completion_manifests (profile_id, state);
54
+ """
55
+
56
+ _REQUIRED_OBLIGATION_COLUMNS = frozenset({
57
+ "obligation_id",
58
+ "operation_id",
59
+ "profile_id",
60
+ "owner",
61
+ "kind",
62
+ "subject_id",
63
+ "revision",
64
+ "state",
65
+ "checksum",
66
+ "detail",
67
+ "attempts",
68
+ "created_at",
69
+ "updated_at",
70
+ })
71
+
72
+ _REQUIRED_MANIFEST_COLUMNS = frozenset({
73
+ "operation_id",
74
+ "profile_id",
75
+ "state",
76
+ "all_met",
77
+ "obligation_count",
78
+ "owner_evidence_json",
79
+ "manifest_hash",
80
+ "created_at",
81
+ "updated_at",
82
+ })
83
+
84
+ _REQUIRED_OBJECTS = frozenset({
85
+ "projection_obligations",
86
+ "completion_manifests",
87
+ "idx_projection_obligations_operation",
88
+ "idx_projection_obligations_profile_state",
89
+ "idx_projection_obligations_state_updated",
90
+ "idx_completion_manifests_profile_state",
91
+ })
92
+
93
+
94
+ def apply(conn: sqlite3.Connection) -> None:
95
+ if verify(conn):
96
+ return
97
+ conn.executescript(DDL)
98
+
99
+
100
+ def repair(conn: sqlite3.Connection) -> None:
101
+ apply(conn)
102
+
103
+
104
+ def verify(conn: sqlite3.Connection) -> bool:
105
+ if not _table_exists(conn, "projection_obligations"):
106
+ return False
107
+ if not _table_exists(conn, "completion_manifests"):
108
+ return False
109
+ if not _REQUIRED_OBLIGATION_COLUMNS <= _columns(conn, "projection_obligations"):
110
+ return False
111
+ if not _REQUIRED_MANIFEST_COLUMNS <= _columns(conn, "completion_manifests"):
112
+ return False
113
+ if not _has_unique_index(conn, "projection_obligations", ("operation_id", "owner", "kind")):
114
+ return False
115
+ present = {
116
+ row[0]
117
+ for row in conn.execute(
118
+ "SELECT name FROM sqlite_master WHERE name IN "
119
+ "(?, ?, ?, ?, ?, ?)",
120
+ tuple(sorted(_REQUIRED_OBJECTS)),
121
+ ).fetchall()
122
+ }
123
+ return _REQUIRED_OBJECTS <= present
124
+
125
+
126
+ def _table_exists(conn: sqlite3.Connection, name: str) -> bool:
127
+ return conn.execute(
128
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name=?",
129
+ (name,),
130
+ ).fetchone() is not None
131
+
132
+
133
+ def _columns(conn: sqlite3.Connection, table: str) -> set[str]:
134
+ return {row[1] for row in conn.execute(f"PRAGMA table_info({table})").fetchall()}
135
+
136
+
137
+ def _has_unique_index(
138
+ conn: sqlite3.Connection, table: str, columns: tuple[str, ...],
139
+ ) -> bool:
140
+ for index in conn.execute(f"PRAGMA index_list({table})").fetchall():
141
+ if not index[2]:
142
+ continue
143
+ index_columns = tuple(
144
+ row[2] for row in conn.execute(f"PRAGMA index_info({index[1]})").fetchall()
145
+ )
146
+ if index_columns == columns:
147
+ return True
148
+ return False
@@ -0,0 +1,58 @@
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
+ import sqlite3
7
+
8
+ NAME = "M034_obligation_integrity"
9
+ DB_TARGET = "memory"
10
+
11
+ DDL = """
12
+ ALTER TABLE projection_obligations ADD COLUMN context_digest TEXT;
13
+ ALTER TABLE projection_obligations ADD COLUMN verify_attempts INTEGER NOT NULL DEFAULT 0;
14
+ ALTER TABLE projection_obligations ADD COLUMN last_verified_at REAL;
15
+ """
16
+
17
+ _ADDED_COLUMNS = {
18
+ "context_digest": "ALTER TABLE projection_obligations ADD COLUMN context_digest TEXT",
19
+ "verify_attempts": (
20
+ "ALTER TABLE projection_obligations "
21
+ "ADD COLUMN verify_attempts INTEGER NOT NULL DEFAULT 0"
22
+ ),
23
+ "last_verified_at": (
24
+ "ALTER TABLE projection_obligations ADD COLUMN last_verified_at REAL"
25
+ ),
26
+ }
27
+
28
+
29
+ def apply(conn: sqlite3.Connection) -> None:
30
+ if not _table_exists(conn):
31
+ return
32
+ existing = _columns(conn)
33
+ for column, statement in _ADDED_COLUMNS.items():
34
+ if column not in existing:
35
+ conn.execute(statement)
36
+
37
+
38
+ def repair(conn: sqlite3.Connection) -> None:
39
+ apply(conn)
40
+
41
+
42
+ def verify(conn: sqlite3.Connection) -> bool:
43
+ if not _table_exists(conn):
44
+ return False
45
+ return set(_ADDED_COLUMNS) <= _columns(conn)
46
+
47
+
48
+ def _table_exists(conn: sqlite3.Connection) -> bool:
49
+ return conn.execute(
50
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name='projection_obligations'"
51
+ ).fetchone() is not None
52
+
53
+
54
+ def _columns(conn: sqlite3.Connection) -> set[str]:
55
+ return {
56
+ row[1]
57
+ for row in conn.execute("PRAGMA table_info(projection_obligations)").fetchall()
58
+ }
@@ -0,0 +1,113 @@
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
+ import sqlite3
7
+
8
+ NAME = "M035_erasure_receipts"
9
+ DB_TARGET = "memory"
10
+
11
+ DDL = """
12
+ CREATE TABLE IF NOT EXISTS erasure_receipts (
13
+ erasure_id TEXT PRIMARY KEY,
14
+ profile_id TEXT NOT NULL,
15
+ subject_type TEXT NOT NULL CHECK (
16
+ subject_type IN ('fact', 'entity', 'profile')
17
+ ),
18
+ subject_id TEXT NOT NULL,
19
+ requested_by TEXT NOT NULL DEFAULT '',
20
+ fact_count INTEGER NOT NULL DEFAULT 0,
21
+ state TEXT NOT NULL CHECK (state IN ('COMPLETE', 'FAILED')),
22
+ all_erased INTEGER NOT NULL DEFAULT 0 CHECK (all_erased IN (0, 1)),
23
+ owner_evidence_json TEXT NOT NULL DEFAULT '[]',
24
+ audit_hash TEXT NOT NULL,
25
+ requested_at REAL NOT NULL,
26
+ completed_at REAL NOT NULL
27
+ );
28
+ CREATE INDEX IF NOT EXISTS idx_erasure_receipts_profile
29
+ ON erasure_receipts (profile_id);
30
+ CREATE INDEX IF NOT EXISTS idx_erasure_receipts_state
31
+ ON erasure_receipts (state);
32
+ CREATE TABLE IF NOT EXISTS projection_tombstones (
33
+ profile_id TEXT NOT NULL,
34
+ fact_id TEXT NOT NULL,
35
+ erasure_id TEXT NOT NULL,
36
+ memory_id TEXT,
37
+ created_at REAL NOT NULL,
38
+ PRIMARY KEY (profile_id, fact_id)
39
+ );
40
+ CREATE INDEX IF NOT EXISTS idx_projection_tombstones_fact
41
+ ON projection_tombstones (fact_id);
42
+ """
43
+
44
+ _REQUIRED_RECEIPT_COLUMNS = frozenset({
45
+ "erasure_id",
46
+ "profile_id",
47
+ "subject_type",
48
+ "subject_id",
49
+ "requested_by",
50
+ "fact_count",
51
+ "state",
52
+ "all_erased",
53
+ "owner_evidence_json",
54
+ "audit_hash",
55
+ "requested_at",
56
+ "completed_at",
57
+ })
58
+
59
+ _REQUIRED_TOMBSTONE_COLUMNS = frozenset({
60
+ "profile_id",
61
+ "fact_id",
62
+ "erasure_id",
63
+ "memory_id",
64
+ "created_at",
65
+ })
66
+
67
+ _REQUIRED_OBJECTS = frozenset({
68
+ "erasure_receipts",
69
+ "projection_tombstones",
70
+ "idx_erasure_receipts_profile",
71
+ "idx_erasure_receipts_state",
72
+ "idx_projection_tombstones_fact",
73
+ })
74
+
75
+
76
+ def apply(conn: sqlite3.Connection) -> None:
77
+ if verify(conn):
78
+ return
79
+ conn.executescript(DDL)
80
+
81
+
82
+ def repair(conn: sqlite3.Connection) -> None:
83
+ apply(conn)
84
+
85
+
86
+ def verify(conn: sqlite3.Connection) -> bool:
87
+ if not _table_exists(conn, "erasure_receipts"):
88
+ return False
89
+ if not _table_exists(conn, "projection_tombstones"):
90
+ return False
91
+ if not _REQUIRED_RECEIPT_COLUMNS <= _columns(conn, "erasure_receipts"):
92
+ return False
93
+ if not _REQUIRED_TOMBSTONE_COLUMNS <= _columns(conn, "projection_tombstones"):
94
+ return False
95
+ present = {
96
+ row[0]
97
+ for row in conn.execute(
98
+ "SELECT name FROM sqlite_master WHERE name IN (?, ?, ?, ?, ?)",
99
+ tuple(sorted(_REQUIRED_OBJECTS)),
100
+ ).fetchall()
101
+ }
102
+ return _REQUIRED_OBJECTS <= present
103
+
104
+
105
+ def _table_exists(conn: sqlite3.Connection, name: str) -> bool:
106
+ return conn.execute(
107
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name=?",
108
+ (name,),
109
+ ).fetchone() is not None
110
+
111
+
112
+ def _columns(conn: sqlite3.Connection, table: str) -> set[str]:
113
+ return {row[1] for row in conn.execute(f"PRAGMA table_info({table})").fetchall()}
@@ -0,0 +1,107 @@
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
+ import sqlite3
7
+
8
+ NAME = "M036_vector_row_map"
9
+ DB_TARGET = "memory"
10
+
11
+ DDL = """
12
+ CREATE TABLE IF NOT EXISTS vector_row_map (
13
+ fact_id TEXT NOT NULL,
14
+ profile_id TEXT NOT NULL,
15
+ vec_rowid INTEGER NOT NULL,
16
+ PRIMARY KEY (fact_id)
17
+ );
18
+ CREATE INDEX IF NOT EXISTS idx_vector_row_map_profile
19
+ ON vector_row_map (profile_id);
20
+ """
21
+
22
+ _REQUIRED_COLUMNS = frozenset({"fact_id", "profile_id", "vec_rowid"})
23
+
24
+ _REQUIRED_OBJECTS = frozenset({
25
+ "vector_row_map",
26
+ "idx_vector_row_map_profile",
27
+ })
28
+
29
+
30
+ def apply(conn: sqlite3.Connection) -> None:
31
+ _reconcile(conn)
32
+
33
+
34
+ def repair(conn: sqlite3.Connection) -> None:
35
+ _reconcile(conn)
36
+
37
+
38
+ def _reconcile(conn: sqlite3.Connection) -> None:
39
+ _rebuild_if_malformed(conn)
40
+ conn.executescript(DDL)
41
+ _backfill(conn)
42
+
43
+
44
+ def _rebuild_if_malformed(conn: sqlite3.Connection) -> None:
45
+ if not _table_exists(conn, "vector_row_map"):
46
+ return
47
+ if _REQUIRED_COLUMNS <= _columns(conn, "vector_row_map"):
48
+ return
49
+ conn.execute("DROP TABLE vector_row_map")
50
+
51
+
52
+ def _backfill(conn: sqlite3.Connection) -> None:
53
+ if not _table_exists(conn, "embedding_metadata"):
54
+ return
55
+ if not {"fact_id", "profile_id", "vec_rowid"} <= _columns(conn, "embedding_metadata"):
56
+ return
57
+ conn.execute(
58
+ "INSERT INTO vector_row_map (fact_id, profile_id, vec_rowid) "
59
+ "SELECT em.fact_id, em.profile_id, em.vec_rowid FROM embedding_metadata em "
60
+ "WHERE em.fact_id IS NOT NULL AND em.vec_rowid IS NOT NULL "
61
+ "AND NOT EXISTS ("
62
+ "SELECT 1 FROM vector_row_map vrm WHERE vrm.fact_id = em.fact_id"
63
+ ")"
64
+ )
65
+
66
+
67
+ def verify(conn: sqlite3.Connection) -> bool:
68
+ if not _table_exists(conn, "vector_row_map"):
69
+ return False
70
+ if not _REQUIRED_COLUMNS <= _columns(conn, "vector_row_map"):
71
+ return False
72
+ present = {
73
+ row[0]
74
+ for row in conn.execute(
75
+ "SELECT name FROM sqlite_master WHERE name IN (?, ?)",
76
+ tuple(sorted(_REQUIRED_OBJECTS)),
77
+ ).fetchall()
78
+ }
79
+ if not _REQUIRED_OBJECTS <= present:
80
+ return False
81
+ return _backfill_complete(conn)
82
+
83
+
84
+ def _backfill_complete(conn: sqlite3.Connection) -> bool:
85
+ if not _table_exists(conn, "embedding_metadata"):
86
+ return True
87
+ if not {"fact_id", "vec_rowid"} <= _columns(conn, "embedding_metadata"):
88
+ return True
89
+ uncovered = conn.execute(
90
+ "SELECT 1 FROM embedding_metadata em "
91
+ "WHERE em.fact_id IS NOT NULL AND em.vec_rowid IS NOT NULL "
92
+ "AND NOT EXISTS ("
93
+ "SELECT 1 FROM vector_row_map vrm WHERE vrm.fact_id = em.fact_id"
94
+ ") LIMIT 1"
95
+ ).fetchone()
96
+ return uncovered is None
97
+
98
+
99
+ def _table_exists(conn: sqlite3.Connection, name: str) -> bool:
100
+ return conn.execute(
101
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name=?",
102
+ (name,),
103
+ ).fetchone() is not None
104
+
105
+
106
+ def _columns(conn: sqlite3.Connection, table: str) -> set[str]:
107
+ return {row[1] for row in conn.execute(f"PRAGMA table_info({table})").fetchall()}
@@ -0,0 +1,162 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later - see LICENSE file
3
+
4
+ """M037 — additive columns for HMAC manifest/receipt versioning.
5
+
6
+ Adds ``manifest_version`` to ``completion_manifests`` and
7
+ ``receipt_version`` to ``erasure_receipts``. Default value 1 means the
8
+ existing rows were written with the unkeyed SHA-256 scheme (v1). New rows
9
+ are written with version 2 (HMAC-SHA256). The verify path reads the
10
+ version column and selects the appropriate verification algorithm.
11
+
12
+ Dependency: M033 (completion_manifests), M035 (erasure_receipts).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import sqlite3
19
+
20
+ NAME = "M037_manifest_hmac_version"
21
+ DB_TARGET = "memory"
22
+
23
+ _ADDED_COLUMNS: dict[str, tuple[str, str]] = {
24
+ "manifest_version_in_completion_manifests": (
25
+ "completion_manifests",
26
+ "manifest_version INTEGER NOT NULL DEFAULT 1",
27
+ ),
28
+ "receipt_version_in_erasure_receipts": (
29
+ "erasure_receipts",
30
+ "receipt_version INTEGER NOT NULL DEFAULT 1",
31
+ ),
32
+ }
33
+
34
+ DDL = "\n".join(
35
+ f"ALTER TABLE {tbl} ADD COLUMN {col_def};"
36
+ for _, (tbl, col_def) in _ADDED_COLUMNS.items()
37
+ )
38
+
39
+
40
+ def apply(conn: sqlite3.Connection) -> None:
41
+ for _key, (table, col_def) in _ADDED_COLUMNS.items():
42
+ if not _table_exists(conn, table):
43
+ continue
44
+ col_name = col_def.split()[0]
45
+ if col_name not in _columns(conn, table):
46
+ conn.execute(f"ALTER TABLE {table} ADD COLUMN {col_def}")
47
+
48
+ # Re-seal pre-existing v1 rows to v2 HMAC so verify_manifest / verify_receipt
49
+ # continue to PASS post-migration. Without re-seal, the verify path rejects
50
+ # v1 rows on v2 DBs as potential downgrade attacks, silently breaking every
51
+ # evidence chain written before M037 was applied.
52
+ _reseal_manifests(conn)
53
+ _reseal_receipts(conn)
54
+
55
+
56
+ def _reseal_manifests(conn: sqlite3.Connection) -> None:
57
+ """Re-seal completion_manifests rows from v1 (unkeyed SHA) to v2 (HMAC)."""
58
+ if not _table_exists(conn, "completion_manifests"):
59
+ return
60
+ if "manifest_version" not in _columns(conn, "completion_manifests"):
61
+ return # column not yet present — nothing to re-seal
62
+
63
+ from superlocalmemory.core.transactions.manifest import (
64
+ MANIFEST_V2,
65
+ _canonical_envelope,
66
+ )
67
+ from superlocalmemory.core.transactions.manifest_key import (
68
+ compute_hmac,
69
+ derive_manifest_hmac_key,
70
+ )
71
+
72
+ key = derive_manifest_hmac_key()
73
+ rows = conn.execute(
74
+ "SELECT operation_id, profile_id, state, all_met, obligation_count, "
75
+ "owner_evidence_json FROM completion_manifests WHERE manifest_version = 1"
76
+ ).fetchall()
77
+ for row in rows:
78
+ evidence_dicts = json.loads(row[5] or "[]")
79
+ canonical = _canonical_envelope(
80
+ operation_id=row[0],
81
+ profile_id=row[1],
82
+ state=row[2],
83
+ all_met=bool(row[3]),
84
+ obligation_count=int(row[4]),
85
+ evidence_dicts=evidence_dicts,
86
+ manifest_version=MANIFEST_V2,
87
+ )
88
+ new_hash = compute_hmac(key, canonical)
89
+ conn.execute(
90
+ "UPDATE completion_manifests SET manifest_hash = ?, manifest_version = ? "
91
+ "WHERE operation_id = ?",
92
+ (new_hash, MANIFEST_V2, row[0]),
93
+ )
94
+
95
+
96
+ def _reseal_receipts(conn: sqlite3.Connection) -> None:
97
+ """Re-seal erasure_receipts rows from v1 (unkeyed SHA) to v2 (HMAC)."""
98
+ if not _table_exists(conn, "erasure_receipts"):
99
+ return
100
+ if "receipt_version" not in _columns(conn, "erasure_receipts"):
101
+ return # column not yet present — nothing to re-seal
102
+
103
+ from superlocalmemory.core.transactions.erasure import (
104
+ _RECEIPT_V2,
105
+ _erasure_canonical,
106
+ )
107
+ from superlocalmemory.core.transactions.manifest_key import (
108
+ compute_hmac,
109
+ derive_receipt_hmac_key,
110
+ )
111
+
112
+ key = derive_receipt_hmac_key()
113
+ rows = conn.execute(
114
+ "SELECT erasure_id, profile_id, subject_type, subject_id, requested_by, "
115
+ "fact_count, state, all_erased, owner_evidence_json, requested_at, completed_at "
116
+ "FROM erasure_receipts WHERE receipt_version = 1"
117
+ ).fetchall()
118
+ for row in rows:
119
+ canonical = _erasure_canonical(
120
+ erasure_id=row[0],
121
+ profile_id=row[1],
122
+ subject_type=row[2],
123
+ subject_id=row[3],
124
+ requested_by=row[4],
125
+ fact_count=int(row[5]),
126
+ state=row[6],
127
+ all_erased=bool(row[7]),
128
+ evidence_json=row[8],
129
+ requested_at=float(row[9]),
130
+ completed_at=float(row[10]),
131
+ receipt_version=_RECEIPT_V2,
132
+ )
133
+ new_hash = compute_hmac(key, canonical)
134
+ conn.execute(
135
+ "UPDATE erasure_receipts SET audit_hash = ?, receipt_version = ? "
136
+ "WHERE erasure_id = ?",
137
+ (new_hash, _RECEIPT_V2, row[0]),
138
+ )
139
+
140
+
141
+ def repair(conn: sqlite3.Connection) -> None:
142
+ apply(conn)
143
+
144
+
145
+ def verify(conn: sqlite3.Connection) -> bool:
146
+ for _key, (table, col_def) in _ADDED_COLUMNS.items():
147
+ if not _table_exists(conn, table):
148
+ return False
149
+ col_name = col_def.split()[0]
150
+ if col_name not in _columns(conn, table):
151
+ return False
152
+ return True
153
+
154
+
155
+ def _table_exists(conn: sqlite3.Connection, name: str) -> bool:
156
+ return conn.execute(
157
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name=?", (name,),
158
+ ).fetchone() is not None
159
+
160
+
161
+ def _columns(conn: sqlite3.Connection, table: str) -> set[str]:
162
+ return {row[1] for row in conn.execute(f"PRAGMA table_info({table})").fetchall()}
@@ -1,8 +1,8 @@
1
1
  # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
2
  # Licensed under AGPL-3.0-or-later - see LICENSE file
3
- # Part of SuperLocalMemory v3.8.11
3
+ # Ported into the SuperLocalMemory V4 migration sequence.
4
4
 
5
- """M033 — add the ``channel`` column to ``learning_feedback``.
5
+ """M038 — add the ``channel`` column to ``learning_feedback``.
6
6
 
7
7
  ``pattern_miner._mine_channel_and_coretrieval`` has always executed::
8
8
 
@@ -35,7 +35,7 @@ from __future__ import annotations
35
35
 
36
36
  import sqlite3
37
37
 
38
- NAME = "M033_learning_feedback_channel"
38
+ NAME = "M038_learning_feedback_channel"
39
39
  DB_TARGET = "learning"
40
40
 
41
41