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
|
@@ -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, '<')
|
|
24
|
+
.replace(/>/g, '>')
|
|
25
|
+
.replace(/"/g, '"')
|
|
26
|
+
.replace(/'/g, ''');
|
|
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
|
-
|
|
679
|
-
|
|
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
|
-
|
|
726
|
-
'Restart
|
|
727
|
-
'
|
|
728
|
-
|
|
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
|
|
917
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
978
|
-
*
|
|
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
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
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
|
-
|
|
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
|
|