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
@@ -0,0 +1,301 @@
1
+ // od-compliance-ext.js — Compliance tab extensions v1.0
2
+ // Extends the OD Operations compliance pane with:
3
+ // Task A: Entity-level GDPR erasure (POST /api/compliance/gdpr/erase-entity)
4
+ // Task B: Erasure Receipts panel (GET /api/compliance/receipts + verify)
5
+ //
6
+ // This module hooks into odRenderOperations by patching window.odRenderOperations
7
+ // to inject the new panels into the compliance tab after the base render.
8
+ //
9
+ // Security: all user-supplied strings go through esc() before innerHTML insertion.
10
+ // Confirm guard on entity erase (typed confirmation + confirmDestructive).
11
+ //
12
+ // Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar — AGPL-3.0
13
+
14
+ /* global window, document, fetch, Promise */
15
+ (function () {
16
+ 'use strict';
17
+
18
+ // ─── Helpers ──────────────────────────────────────────────────────────────
19
+
20
+ function esc(s) {
21
+ return String(s == null ? '' : s)
22
+ .replace(/&/g, '&')
23
+ .replace(/</g, '&lt;')
24
+ .replace(/>/g, '&gt;')
25
+ .replace(/"/g, '&quot;')
26
+ .replace(/'/g, '&#39;');
27
+ }
28
+
29
+ function fmtTime(ts) {
30
+ if (!ts) return '—';
31
+ try {
32
+ var d = typeof ts === 'number' ? new Date(ts * 1000) : new Date(ts);
33
+ return d.toLocaleString();
34
+ } catch (e) { return String(ts); }
35
+ }
36
+
37
+ // ─── Task A: Entity Erase Card ────────────────────────────────────────────
38
+
39
+ var ENTITY_ERASE_ID = 'od-entity-erase-card';
40
+
41
+ function buildEntityEraseCard() {
42
+ var card = document.createElement('div');
43
+ card.id = ENTITY_ERASE_ID;
44
+ card.className = 'card';
45
+ card.style.marginTop = '16px';
46
+ // Using textContent / controlled HTML (only static strings + esc'd values below)
47
+ card.innerHTML =
48
+ '<div class="card-head">' +
49
+ '<h3>Entity-level GDPR erasure</h3>' +
50
+ '</div>' +
51
+ '<div class="card-pad">' +
52
+ '<p class="muted" style="font-size:13px" id="od-entity-erase-summary">' +
53
+ 'Erase all facts mentioning a specific named entity (GDPR Art. 17). ' +
54
+ 'Enter the entity name exactly; you will need to confirm it. This is IRREVERSIBLE.' +
55
+ '</p>' +
56
+ '<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:10px">' +
57
+ '<input id="od-entity-erase-input" type="text" maxlength="200" ' +
58
+ 'placeholder="Entity name…" ' +
59
+ 'style="flex:1;min-width:180px;padding:7px 10px;border-radius:8px;' +
60
+ 'border:1px solid var(--border);background:var(--card);color:var(--fg);font-size:13px">' +
61
+ '<button id="od-entity-erase-btn" class="btn sm" ' +
62
+ 'style="border-color:#ff6b6b;color:#ff6b6b">Erase entity…</button>' +
63
+ '</div>' +
64
+ '</div>';
65
+ return card;
66
+ }
67
+
68
+ function wireEntityEraseCard() {
69
+ var btn = document.getElementById('od-entity-erase-btn');
70
+ var input = document.getElementById('od-entity-erase-input');
71
+ var summary = document.getElementById('od-entity-erase-summary');
72
+ if (!btn || !input) return;
73
+
74
+ btn.addEventListener('click', function () {
75
+ var entityName = (input.value || '').trim();
76
+ if (!entityName) {
77
+ if (summary) summary.textContent = 'Enter an entity name first.';
78
+ return;
79
+ }
80
+ // Step 1: typed-confirmation prompt (same guard as profile erase)
81
+ var typed = window.prompt(
82
+ 'This PERMANENTLY erases all facts mentioning entity "' + entityName + '".\n' +
83
+ 'This cannot be undone.\n\nType the entity name to confirm:'
84
+ );
85
+ if (typed === null) return; // cancelled
86
+ if (typed !== entityName) {
87
+ if (summary) summary.textContent = 'Erase cancelled — name did not match.';
88
+ return;
89
+ }
90
+ // Step 2: confirmDestructive modal
91
+ window.confirmDestructive({
92
+ title: 'Erase entity',
93
+ target: entityName,
94
+ consequence: 'All facts mentioning this entity will be permanently erased.',
95
+ confirmLabel: 'Erase entity',
96
+ }).then(function (confirmed) {
97
+ if (!confirmed) return;
98
+ btn.disabled = true;
99
+ btn.textContent = 'Erasing…';
100
+ if (summary) summary.textContent = 'Erasing entity "' + esc(entityName) + '"…';
101
+ fetch('/api/compliance/gdpr/erase-entity', {
102
+ method: 'POST',
103
+ headers: { 'Content-Type': 'application/json' },
104
+ body: JSON.stringify({ entity_name: entityName, confirm: entityName }),
105
+ })
106
+ .then(function (r) { return r.json(); })
107
+ .then(function (d) {
108
+ if (!summary) return;
109
+ if (d && d.success) {
110
+ var parts = [];
111
+ if (d.facts != null) parts.push('facts erased: ' + d.facts);
112
+ if (d.entity != null) parts.push('entity: ' + esc(String(d.entity)));
113
+ summary.textContent = 'Entity erased — ' + (parts.join(', ') || 'done') + '.';
114
+ input.value = '';
115
+ // Reload receipts panel to reflect new receipt
116
+ loadReceiptsPanel();
117
+ } else {
118
+ summary.textContent = 'Erase failed: ' + esc(String((d && d.error) || 'unknown'));
119
+ }
120
+ })
121
+ .catch(function () {
122
+ if (summary) summary.textContent = 'Erase request failed. Check console.';
123
+ })
124
+ .finally(function () {
125
+ btn.disabled = false;
126
+ btn.textContent = 'Erase entity…';
127
+ });
128
+ });
129
+ });
130
+ }
131
+
132
+ // ─── Task B: Erasure Receipts Panel ───────────────────────────────────────
133
+
134
+ var RECEIPTS_ID = 'od-receipts-card';
135
+
136
+ function buildReceiptsCard() {
137
+ var card = document.createElement('div');
138
+ card.id = RECEIPTS_ID;
139
+ card.className = 'card';
140
+ card.style.marginTop = '16px';
141
+ card.innerHTML =
142
+ '<div class="card-head">' +
143
+ '<h3>Erasure receipts</h3>' +
144
+ '<div class="spacer"></div>' +
145
+ '<button class="btn sm" id="od-receipts-refresh-btn">Refresh</button>' +
146
+ '</div>' +
147
+ '<div class="card-pad">' +
148
+ '<table class="tbl" id="od-receipts-tbl">' +
149
+ '<thead><tr>' +
150
+ '<th>ID</th>' +
151
+ '<th>Type</th>' +
152
+ '<th>Subject</th>' +
153
+ '<th>State</th>' +
154
+ '<th>Facts</th>' +
155
+ '<th>Requested</th>' +
156
+ '<th></th>' +
157
+ '</tr></thead>' +
158
+ '<tbody id="od-receipts-body">' +
159
+ '<tr><td colspan="7" class="dim" style="text-align:center;padding:20px">Loading…</td></tr>' +
160
+ '</tbody>' +
161
+ '</table>' +
162
+ '</div>';
163
+ return card;
164
+ }
165
+
166
+ function loadReceiptsPanel() {
167
+ var tbody = document.getElementById('od-receipts-body');
168
+ if (!tbody) return;
169
+ tbody.innerHTML = '<tr><td colspan="7" class="dim" style="text-align:center;padding:16px">Loading…</td></tr>';
170
+ fetch('/api/compliance/receipts?limit=50')
171
+ .then(function (r) { return r.ok ? r.json() : null; })
172
+ .then(function (d) {
173
+ if (!d || !d.available) {
174
+ tbody.innerHTML = '<tr><td colspan="7" class="dim" style="text-align:center;padding:16px">Receipts unavailable.</td></tr>';
175
+ return;
176
+ }
177
+ var receipts = d.receipts || [];
178
+ if (receipts.length === 0) {
179
+ tbody.innerHTML = '<tr><td colspan="7" class="dim" style="text-align:center;padding:16px">No erasure receipts yet.</td></tr>';
180
+ return;
181
+ }
182
+ tbody.innerHTML = receipts.map(function (r) {
183
+ var shortId = esc(String(r.erasure_id || '').slice(0, 8)) + '…';
184
+ var stateClass = r.state === 'COMPLETE' ? 'ok' : 'danger';
185
+ var allOk = r.all_erased ? '<span class="badge ok">all erased</span>' : '<span class="badge warn">partial</span>';
186
+ return (
187
+ '<tr>' +
188
+ '<td class="mono dim" style="font-size:11px" title="' + esc(r.erasure_id || '') + '">' + shortId + '</td>' +
189
+ '<td><span class="badge neutral">' + esc(r.subject_type || '') + '</span></td>' +
190
+ '<td class="dim" style="font-size:12px;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' +
191
+ esc(r.subject_id || '') +
192
+ '</td>' +
193
+ '<td><span class="badge ' + stateClass + '">' + esc(r.state || '') + '</span></td>' +
194
+ '<td class="num dim">' + esc(String(r.fact_count || 0)) + '</td>' +
195
+ '<td class="dim" style="font-size:11px">' + esc(fmtTime(r.requested_at)) + '</td>' +
196
+ '<td><button class="btn sm ghost" data-verify-id="' + esc(r.erasure_id || '') + '">Verify</button></td>' +
197
+ '</tr>'
198
+ );
199
+ }).join('');
200
+ // Wire verify buttons
201
+ tbody.querySelectorAll('[data-verify-id]').forEach(function (btn) {
202
+ btn.addEventListener('click', function () {
203
+ var eid = btn.getAttribute('data-verify-id');
204
+ btn.disabled = true;
205
+ btn.textContent = '…';
206
+ fetch('/api/compliance/receipts/' + encodeURIComponent(eid) + '/verify')
207
+ .then(function (r) { return r.ok ? r.json() : null; })
208
+ .then(function (d) {
209
+ if (d && d.verified === true) {
210
+ btn.textContent = 'Verified ✓';
211
+ btn.style.color = 'var(--ok)';
212
+ } else if (d && d.verified === false) {
213
+ btn.textContent = 'Tampered!';
214
+ btn.style.color = 'var(--danger)';
215
+ } else {
216
+ btn.textContent = 'Error';
217
+ }
218
+ btn.disabled = false;
219
+ })
220
+ .catch(function () { btn.textContent = 'Error'; btn.disabled = false; });
221
+ });
222
+ });
223
+ })
224
+ .catch(function () {
225
+ var tb = document.getElementById('od-receipts-body');
226
+ if (tb) tb.innerHTML = '<tr><td colspan="7" class="dim" style="text-align:center;padding:16px">Failed to load receipts.</td></tr>';
227
+ });
228
+ }
229
+
230
+ function wireReceiptsPanel() {
231
+ var refreshBtn = document.getElementById('od-receipts-refresh-btn');
232
+ if (refreshBtn) {
233
+ refreshBtn.addEventListener('click', loadReceiptsPanel);
234
+ }
235
+ loadReceiptsPanel();
236
+ }
237
+
238
+ // ─── Injection Hook ───────────────────────────────────────────────────────
239
+
240
+ /**
241
+ * Inject the two new panels into the compliance tab section.
242
+ * Called after odRenderOperations() has built and populated the DOM.
243
+ */
244
+ function injectCompliancePanels(container) {
245
+ // Find the compliance tabpane
246
+ var pane = container && container.querySelector('[data-od-pane="compliance"]');
247
+ if (!pane) return;
248
+
249
+ // Remove any stale injected panels from a previous render
250
+ var oldErase = pane.querySelector('#' + ENTITY_ERASE_ID);
251
+ if (oldErase) oldErase.remove();
252
+ var oldReceipts = pane.querySelector('#' + RECEIPTS_ID);
253
+ if (oldReceipts) oldReceipts.remove();
254
+
255
+ // Append the two new cards at the bottom of the compliance tab
256
+ pane.appendChild(buildEntityEraseCard());
257
+ pane.appendChild(buildReceiptsCard());
258
+
259
+ // Wire interactions
260
+ wireEntityEraseCard();
261
+ wireReceiptsPanel();
262
+ }
263
+
264
+ // ─── Patch odRenderOperations ─────────────────────────────────────────────
265
+
266
+ /**
267
+ * Wait for od-operations.js to define window.odRenderOperations, then wrap it
268
+ * so our panels are injected after every render. Uses polling with a cap —
269
+ * no unbounded loops.
270
+ */
271
+ function patchOdRenderOperations() {
272
+ var base = window.odRenderOperations;
273
+ if (typeof base !== 'function') return false;
274
+ window.odRenderOperations = function (container) {
275
+ base(container);
276
+ // od-operations.js renders asynchronously (Promise.all inside).
277
+ // Give it time to populate the DOM before injecting our panels.
278
+ setTimeout(function () { injectCompliancePanels(container); }, 600);
279
+ };
280
+ return true;
281
+ }
282
+
283
+ // Retry for up to 3s in case od-operations.js hasn't defined the function yet.
284
+ var _attempts = 0;
285
+ function tryPatch() {
286
+ if (patchOdRenderOperations()) return;
287
+ _attempts += 1;
288
+ if (_attempts < 30) setTimeout(tryPatch, 100);
289
+ }
290
+
291
+ // Also inject into the currently active operations pane on DOMContentLoaded
292
+ document.addEventListener('DOMContentLoaded', function () {
293
+ tryPatch();
294
+ // If the operations pane is already in the DOM and populated, inject now
295
+ var pane = document.getElementById('operations-pane');
296
+ if (pane && pane.querySelector('[data-od-pane="compliance"]')) {
297
+ injectCompliancePanels(pane);
298
+ }
299
+ });
300
+
301
+ }());
@@ -403,11 +403,24 @@
403
403
  '</div>' +
404
404
  '</div>' +
405
405
 
406
- // Audit trail with All/Denied segment filter
406
+ // Audit trail with All/Denied segment filter + event-type API filter
407
407
  '<div class="card" style="margin-top:16px">' +
408
408
  '<div class="card-head">' +
409
409
  '<h3>Audit trail</h3>' +
410
+ // Task C: chain-integrity badge
411
+ '<span id="od-audit-chain-status"></span>' +
410
412
  '<div class="spacer"></div>' +
413
+ // Task C: event-type filter select (calls API)
414
+ '<select id="od-audit-evt-filter" style="padding:4px 8px;border-radius:6px;' +
415
+ 'border:1px solid var(--border);background:var(--card);color:var(--fg);font-size:12px">' +
416
+ '<option value="">All types</option>' +
417
+ '<option value="recall">recall</option>' +
418
+ '<option value="remember">remember</option>' +
419
+ '<option value="delete">delete</option>' +
420
+ '<option value="lifecycle_transition">lifecycle</option>' +
421
+ '<option value="access_denied">access_denied</option>' +
422
+ '<option value="retention_enforced">retention</option>' +
423
+ '</select>' +
411
424
  '<div class="seg" id="od-audit-filter">' +
412
425
  '<button class="active" data-filter="all">All</button>' +
413
426
  '<button data-filter="denied">Denied</button>' +
@@ -675,8 +688,14 @@
675
688
 
676
689
  if (applyBtn) {
677
690
  applyBtn.addEventListener('click', function () {
678
- if (!window.confirm('This will transition memories to lower lifecycle states. Continue?')) return;
679
- applyBtn.disabled = true;
691
+ window.confirmDestructive({
692
+ title: 'Apply compaction',
693
+ target: 'All eligible memories',
694
+ consequence: 'Transitions memories to lower lifecycle states.',
695
+ confirmLabel: 'Apply',
696
+ }).then(function(confirmed) {
697
+ if (!confirmed) return;
698
+ applyBtn.disabled = true;
680
699
  applyBtn.textContent = 'Running…';
681
700
 
682
701
  fetch('/api/lifecycle/compact', {
@@ -708,6 +727,7 @@
708
727
  applyBtn.disabled = false;
709
728
  applyBtn.textContent = 'Apply compaction';
710
729
  });
730
+ });
711
731
  });
712
732
  }
713
733
  }
@@ -722,10 +742,14 @@
722
742
  var summary = document.getElementById('od-restart-summary');
723
743
  if (!btn) return;
724
744
  btn.addEventListener('click', function () {
725
- if (!window.confirm(
726
- 'Restart the memory daemon now? It will be unavailable for a few ' +
727
- 'seconds. Your memories are not affected.')) return;
728
- btn.disabled = true;
745
+ window.confirmDestructive({
746
+ title: 'Restart memory daemon',
747
+ target: 'Memory daemon',
748
+ consequence: 'Will be unavailable for a few seconds. Your memories are not affected.',
749
+ confirmLabel: 'Restart',
750
+ }).then(function(confirmed) {
751
+ if (!confirmed) return;
752
+ btn.disabled = true;
729
753
  btn.textContent = 'Restarting…';
730
754
  if (summary) summary.textContent = 'Restart requested — waiting for the daemon to come back…';
731
755
 
@@ -782,6 +806,7 @@
782
806
  if (tries > 30) { clearInterval(poll); btn.disabled = false; btn.textContent = 'Restart daemon'; }
783
807
  }, 1000);
784
808
  });
809
+ });
785
810
  });
786
811
  }
787
812
 
@@ -903,40 +928,81 @@
903
928
 
904
929
  var ACTION_CLS = { archive: 'neutral', tombstone: 'danger', notify: 'warn' };
905
930
 
931
+ // Build rows then wire delete buttons post-inject (avoids inline onclick — CSP safe)
906
932
  retTbody.innerHTML = policies.map(function (p) {
907
933
  var ac = ACTION_CLS[p.action] || 'neutral';
908
- // CRIT(c): name, category, action through esc()
909
934
  return (
910
935
  '<tr>' +
911
936
  '<td><b>' + esc(p.name || '') + '</b></td>' +
912
937
  '<td class="num">' + esc(String(p.retention_days || '—')) + '</td>' +
913
938
  '<td><span class="badge neutral">' + esc(p.category || 'all') + '</span></td>' +
914
939
  '<td><span class="badge ' + ac + '">' + esc(p.action || '') + '</span></td>' +
940
+ // Task E: per-row delete — data-pol-name used by wireRetentionDeleteBtns()
915
941
  '<td style="text-align:right">' +
916
- '<button class="btn sm ghost" disabled>Edit</button>' +
917
- // TODO: wire edit button to /api/compliance/retention-policy PATCH when available
942
+ '<button class="btn sm danger" data-pol-name="' + esc(p.name || '') + '">' +
943
+ 'Delete' +
944
+ '</button>' +
918
945
  '</td>' +
919
946
  '</tr>'
920
947
  );
921
948
  }).join('');
949
+
950
+ // Task E: wire delete buttons (CSP-safe addEventListener after innerHTML set)
951
+ wireRetentionDeleteBtns(retTbody);
922
952
  }
923
953
 
924
954
  // Cache of all audit rows (for client-side All/Denied filter)
925
955
  var _allAuditRows = [];
926
956
  /**
927
- * Populate the audit trail table.
957
+ * Task C: Populate the audit trail table.
958
+ * auditFull: response from /api/compliance/audit (preferred, up to 100 events).
959
+ * comp: response from /api/compliance/status (fallback, recent_audit_events).
928
960
  * CRIT(c): actor, action, target, result all through esc().
929
961
  */
930
- function populateAuditTrail(comp) {
962
+ function populateAuditTrail(comp, auditFull) {
931
963
  var auditTbody = document.getElementById('od-audit-body');
932
964
  if (!auditTbody) return;
933
965
 
934
- var events = (comp && comp.recent_audit_events) || [];
966
+ // Prefer full audit endpoint data; fall back to status data
967
+ var events = (auditFull && auditFull.events) || (comp && comp.recent_audit_events) || [];
935
968
  _allAuditRows = events;
936
969
 
937
970
  renderAuditRows(auditTbody, events);
938
971
  }
939
972
 
973
+ /**
974
+ * Task C: Render the chain-integrity badge inside the audit trail card header.
975
+ * Appended to #od-audit-chain-status if it exists.
976
+ */
977
+ function showAuditChainStatus(auditFull) {
978
+ var el = document.getElementById('od-audit-chain-status');
979
+ if (!el || !auditFull) return;
980
+ var ok = auditFull.chain_verified !== false;
981
+ el.innerHTML = ok
982
+ ? '<span class="badge ok" style="margin-left:8px">Chain verified</span>'
983
+ : '<span class="badge danger" style="margin-left:8px">Chain integrity issue</span>';
984
+ }
985
+
986
+ /** Task C: re-fetch audit trail from API with a specific event_type filter. */
987
+ function fetchAndRenderAudit(eventType) {
988
+ var auditTbody = document.getElementById('od-audit-body');
989
+ if (!auditTbody) return;
990
+ auditTbody.innerHTML = '<tr><td colspan="6" class="dim" style="text-align:center;padding:16px">Loading…</td></tr>';
991
+ var url = '/api/compliance/audit?limit=100';
992
+ if (eventType && eventType !== 'all') url += '&event_type=' + encodeURIComponent(eventType);
993
+ fetch(url)
994
+ .then(function (r) { return r.ok ? r.json() : null; })
995
+ .then(function (d) {
996
+ if (!d) return;
997
+ _allAuditRows = d.events || [];
998
+ renderAuditRows(auditTbody, _allAuditRows);
999
+ showAuditChainStatus(d);
1000
+ })
1001
+ .catch(function () {
1002
+ if (auditTbody) auditTbody.innerHTML = '<tr><td colspan="6" class="dim" style="text-align:center;padding:16px">Failed to load audit trail.</td></tr>';
1003
+ });
1004
+ }
1005
+
940
1006
  /** Render audit table rows into tbody, filtering by mode ('all' or 'denied'). */
941
1007
  function renderAuditRows(tbody, events) {
942
1008
  if (events.length === 0) {
@@ -974,14 +1040,16 @@
974
1040
  }).join('');
975
1041
  }
976
1042
  /**
977
- * Wire the All/Denied segment filter in the audit trail header.
978
- * Client-side filter over already-fetched data — no extra network call.
1043
+ * Task C: Wire audit filters.
1044
+ * - All/Denied segment: client-side filter (fast).
1045
+ * - Event-type select (#od-audit-evt-filter): calls API to get filtered results.
979
1046
  */
980
- function wireAuditFilter() {
1047
+ function wireAuditFilter(auditFull) {
981
1048
  var filterEl = document.getElementById('od-audit-filter');
982
1049
  var tbody = document.getElementById('od-audit-body');
983
1050
  if (!filterEl || !tbody) return;
984
1051
 
1052
+ // All / Denied segment buttons — client-side
985
1053
  filterEl.querySelectorAll('button').forEach(function (btn) {
986
1054
  btn.addEventListener('click', function () {
987
1055
  var mode = btn.getAttribute('data-filter');
@@ -1000,6 +1068,54 @@
1000
1068
  renderAuditRows(tbody, rows);
1001
1069
  });
1002
1070
  });
1071
+
1072
+ // Task C: Event-type select — re-fetches from API
1073
+ var evtSelect = document.getElementById('od-audit-evt-filter');
1074
+ if (evtSelect) {
1075
+ evtSelect.addEventListener('change', function () {
1076
+ fetchAndRenderAudit(evtSelect.value);
1077
+ });
1078
+ }
1079
+ }
1080
+
1081
+ /**
1082
+ * Task E: Wire delete buttons injected by populateRetentionPolicies.
1083
+ * Data attribute data-pol-name carries the policy name (already esc'd in innerHTML).
1084
+ * Uses confirmDestructive before calling DELETE /api/compliance/retention-policy?name=.
1085
+ */
1086
+ function wireRetentionDeleteBtns(tbody) {
1087
+ tbody.querySelectorAll('[data-pol-name]').forEach(function (btn) {
1088
+ btn.addEventListener('click', function () {
1089
+ var policyName = btn.getAttribute('data-pol-name');
1090
+ window.confirmDestructive({
1091
+ title: 'Delete retention policy',
1092
+ target: policyName,
1093
+ consequence: 'This policy will no longer be enforced on any memory.',
1094
+ confirmLabel: 'Delete',
1095
+ }).then(function (confirmed) {
1096
+ if (!confirmed) return;
1097
+ btn.disabled = true;
1098
+ fetch('/api/compliance/retention-policy?name=' + encodeURIComponent(policyName), {
1099
+ method: 'DELETE',
1100
+ })
1101
+ .then(function (r) { return r.json(); })
1102
+ .then(function (d) {
1103
+ if (d && d.success !== false) {
1104
+ var pane = document.getElementById('operations-pane');
1105
+ if (pane && typeof window.odRenderOperations === 'function') {
1106
+ window.odRenderOperations(pane);
1107
+ }
1108
+ } else {
1109
+ btn.disabled = false;
1110
+ if (typeof console !== 'undefined') {
1111
+ console.error('[od-ops] retention delete failed:', d && d.error);
1112
+ }
1113
+ }
1114
+ })
1115
+ .catch(function () { btn.disabled = false; });
1116
+ });
1117
+ });
1118
+ });
1003
1119
  }
1004
1120
 
1005
1121
  /**
@@ -1043,11 +1159,18 @@
1043
1159
  })
1044
1160
  .then(function (r) { return r.json(); })
1045
1161
  .then(function (d) {
1046
- if (summary) {
1047
- summary.textContent = d && d.success
1048
- ? ('Retention applied — archived ' + (d.archived || 0) +
1049
- ', tombstoned ' + (d.tombstoned || 0) + ', flagged ' + (d.notified || 0) + '.')
1050
- : ('Retention failed: ' + ((d && d.error) || 'unknown'));
1162
+ if (!summary) return;
1163
+ // Task E: surface all returned counts from the enforce response
1164
+ if (d && d.success) {
1165
+ var parts = [];
1166
+ if (d.archived != null) parts.push('archived ' + fmtNum(d.archived));
1167
+ if (d.tombstoned != null) parts.push('tombstoned ' + fmtNum(d.tombstoned));
1168
+ if (d.notified != null) parts.push('flagged ' + fmtNum(d.notified));
1169
+ if (d.evaluated != null) parts.push('evaluated ' + fmtNum(d.evaluated));
1170
+ if (d.policies_run != null) parts.push('policies run: ' + fmtNum(d.policies_run));
1171
+ summary.textContent = 'Retention applied — ' + (parts.join(', ') || 'no changes needed') + '.';
1172
+ } else {
1173
+ summary.textContent = 'Retention failed: ' + ((d && d.error) || 'unknown');
1051
1174
  }
1052
1175
  })
1053
1176
  .catch(function () { if (summary) summary.textContent = 'Retention run failed. Check console.'; })
@@ -1216,10 +1339,13 @@
1216
1339
  fetch('/api/lifecycle/status').then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; }),
1217
1340
  fetch('/api/trust/stats').then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; }),
1218
1341
  fetch('/api/compliance/status').then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; }),
1342
+ // Task C: wire the full audit trail endpoint (up to 100 events, chain_verified)
1343
+ fetch('/api/compliance/audit?limit=100').then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; }),
1219
1344
  ]).then(function (results) {
1220
1345
  var lc = results[0];
1221
1346
  var trustStats = results[1];
1222
1347
  var comp = results[2];
1348
+ var auditFull = results[3]; // {available, events, chain_verified, ...}
1223
1349
 
1224
1350
  // Inject .ctl class styles (used by Trust section control rows)
1225
1351
  injectCtlStyle();
@@ -1243,10 +1369,15 @@
1243
1369
  // Populate compliance
1244
1370
  populateComplianceKpi(comp);
1245
1371
  populateRetentionPolicies(comp);
1246
- populateAuditTrail(comp);
1372
+ // Task C: prefer the full audit trail from the dedicated endpoint;
1373
+ // fall back to the status endpoint's recent_audit_events on failure.
1374
+ populateAuditTrail(comp, auditFull);
1375
+
1376
+ // Task C: show chain-integrity indicator
1377
+ showAuditChainStatus(auditFull);
1247
1378
 
1248
1379
  // Wire audit filter and new-policy button (listeners on injected DOM)
1249
- wireAuditFilter();
1380
+ wireAuditFilter(auditFull);
1250
1381
  wireNewPolicyBtn();
1251
1382
  wireGdprBtns();
1252
1383