From 8d923ba9627f303d981733c66a755dca6353121b Mon Sep 17 00:00:00 2001
From: jensmohr
Date: Wed, 10 Jun 2026 23:39:07 +0200
Subject: [PATCH] Consolidate GVL deletion workflow and document purge
semantics
---
docs/architecture/gvl-purge-notes.md | 28 +++++
src/background.js | 13 +-
src/background/db/db-constants.js | 13 --
src/background/db/db-retention.js | 30 +----
src/core/gvl-evidence-json.js | 74 ++++++++++--
src/dashboard/dashboard.html | 19 +--
src/data-maintenance/data-maintenance.css | 4 +
src/data-maintenance/data-maintenance.html | 13 +-
src/data-maintenance/data-maintenance.js | 132 ++++++++++++++-------
src/gvl-explorer/gvl-explorer.js | 15 ++-
10 files changed, 230 insertions(+), 111 deletions(-)
create mode 100644 docs/architecture/gvl-purge-notes.md
diff --git a/docs/architecture/gvl-purge-notes.md b/docs/architecture/gvl-purge-notes.md
new file mode 100644
index 0000000..a8eb3a0
--- /dev/null
+++ b/docs/architecture/gvl-purge-notes.md
@@ -0,0 +1,28 @@
+### GVL-Purge-Summary und Snapshot-Events
+
+Bei der Untersuchung der GVL-Bereinigung wurde festgestellt, dass die Purge-Summary je nach Herkunft einer GVL-Revision unterschiedliche Gesamtzahlen ausweisen kann.
+
+Beobachtung:
+
+* Vault-GVL importiert → Purge-Summary: `13593 Records`
+* Web-GVL geladen, passende Vault-GVL verifiziert → Purge-Summary: `13594 Records`
+
+Die Differenz von genau einem Record stellt keinen Zählfehler dar.
+
+Ursache:
+
+Der Web-Ingest einer offiziellen GVL erzeugt zusätzlich einen Eintrag im Store `gvl_snapshot_events`. Dabei handelt es sich um ein Ereignis des lokalen Beobachtungsverlaufs, beispielsweise `gvl_snapshot_ingested`.
+
+Der Import einer GVL-Revision aus einem Vault-Paket rekonstruiert dagegen die Revision selbst, importiert jedoch keine Snapshot-Event-Historie und erzeugt auch kein entsprechendes Ereignis.
+
+Die globale Purge-Summary zählt `gvl_snapshot_events` mit. Daher entsteht im Web-Pfad gegenüber dem Vault-Pfad ein zusätzlicher gelöschter Record.
+
+Fachliche Einordnung:
+
+Die unterschiedliche Gesamtzahl ist erwartetes Verhalten und Ausdruck unterschiedlicher Provenienz:
+
+* Web-GVL: dokumentierter lokaler Ingest-Vorgang.
+* Vault-GVL: rekonstruiertes Evidenzobjekt ohne lokale Beobachtungshistorie.
+
+Die Purge-Summary ist daher nicht nur ein Maß für die Größe einer Revision, sondern kann auch Unterschiede im Entstehungskontext der lokalen Evidence widerspiegeln.
+
diff --git a/src/background.js b/src/background.js
index 8bd3791..9781a36 100644
--- a/src/background.js
+++ b/src/background.js
@@ -125,10 +125,6 @@ async function handleVendorGetMessage(message, sender) {
return handlePurgeUnlockedEvidenceRecordsMessage();
}
- if (message.type === "purge_gvl_reference_data") {
- return handlePurgeGvlReferenceDataMessage();
- }
-
if (message.type === "delete_all_evidence_database") {
return handleDeleteAllEvidenceDatabaseMessage();
}
@@ -302,7 +298,8 @@ async function handleMarkGvlRevisionEvidenceVaultCopyMessage(message) {
return {
success: true,
mark: await markVendorGetGvlRevisionEvidenceVaultCopy(
- message?.payload?.snapshotSha256 ?? null
+ message?.payload?.snapshotSha256 ?? null,
+ message?.payload?.verification ?? null
)
};
} catch (error) {
@@ -1229,12 +1226,6 @@ async function handlePurgeUnlockedEvidenceRecordsMessage() {
return purgeUnlockedEvidenceRecords(db);
}
-async function handlePurgeGvlReferenceDataMessage() {
- const db = await openVendorGetDb();
-
- return purgeGvlReferenceData(db);
-}
-
function handleDeleteAllEvidenceDatabaseMessage() {
return deleteVendorGetDatabase();
}
diff --git a/src/background/db/db-constants.js b/src/background/db/db-constants.js
index a2aebec..12dae18 100644
--- a/src/background/db/db-constants.js
+++ b/src/background/db/db-constants.js
@@ -33,16 +33,3 @@ const VENDORGET_EVIDENCE_STORE_NAMES = [
VENDORGET_STORE_NAMES.gvlDataCategories,
VENDORGET_STORE_NAMES.gvlVendorRelationships
];
-
-const VENDORGET_GVL_REFERENCE_STORE_NAMES = [
- VENDORGET_STORE_NAMES.gvlRawEvidence,
- VENDORGET_STORE_NAMES.gvlSnapshots,
- VENDORGET_STORE_NAMES.gvlSnapshotEvents,
- VENDORGET_STORE_NAMES.gvlVendors,
- VENDORGET_STORE_NAMES.gvlPurposes,
- VENDORGET_STORE_NAMES.gvlSpecialPurposes,
- VENDORGET_STORE_NAMES.gvlFeatures,
- VENDORGET_STORE_NAMES.gvlSpecialFeatures,
- VENDORGET_STORE_NAMES.gvlDataCategories,
- VENDORGET_STORE_NAMES.gvlVendorRelationships
-];
diff --git a/src/background/db/db-retention.js b/src/background/db/db-retention.js
index a561e0f..00fb037 100644
--- a/src/background/db/db-retention.js
+++ b/src/background/db/db-retention.js
@@ -89,31 +89,11 @@ async function purgeUnlockedEvidenceRecords(db) {
});
}
-function purgeGvlReferenceData(db) {
- return new Promise((resolve, reject) => {
- const clearedStores = {};
- const tx = db.transaction(VENDORGET_GVL_REFERENCE_STORE_NAMES, "readwrite");
-
- tx.onerror = () => reject(tx.error);
- tx.onabort = () => reject(tx.error);
- tx.oncomplete = () => {
- resolve({
- success: true,
- clearedStores
- });
- };
-
- for (const storeName of VENDORGET_GVL_REFERENCE_STORE_NAMES) {
- const objectStore = tx.objectStore(storeName);
- const countRequest = objectStore.count();
-
- countRequest.onsuccess = () => {
- clearedStores[storeName] = countRequest.result;
- objectStore.clear();
- };
- }
- });
-}
+// TODO: GVL-Datenpflege darf nicht storeweise per clear() erfolgen.
+// Loeschbar ist nur eine GVL-Revision, wenn ihre zugehoerigen Raw-, Snapshot-,
+// Event- und normalisierten Records identifiziert sind, ihr Schutzstatus
+// vollstaendig bewertet wurde und eine vorhandene Vault-/Workspace-Schutzlogik
+// die Loeschung erlaubt.
function buildGvlWorkspaceProtectionIndex(db) {
return new Promise((resolve, reject) => {
diff --git a/src/core/gvl-evidence-json.js b/src/core/gvl-evidence-json.js
index 8a5ea83..c1e7277 100644
--- a/src/core/gvl-evidence-json.js
+++ b/src/core/gvl-evidence-json.js
@@ -287,14 +287,21 @@ async function importVendorGetGvlRevisionEvidenceJson(exportContainer) {
};
}
-async function markVendorGetGvlRevisionEvidenceVaultCopy(snapshotSha256) {
+async function markVendorGetGvlRevisionEvidenceVaultCopy(
+ snapshotSha256,
+ verification = null
+) {
if (!snapshotSha256) {
throw new Error("missing_snapshot_sha256");
}
const db = await openVendorGetDb();
- return markGvlRevisionEvidenceVaultCopyAvailable(db, snapshotSha256);
+ return markGvlRevisionEvidenceVaultCopyAvailable(
+ db,
+ snapshotSha256,
+ verification
+ );
}
function getGvlEvidenceRecordByKey(db, storeName, key) {
@@ -632,9 +639,16 @@ function formatGvlEvidenceProvenance(values) {
return "web";
}
-function markGvlRevisionEvidenceVaultCopyAvailable(db, snapshotSha256) {
- return updateGvlRevisionEvidenceRecords(db, snapshotSha256, (record) =>
- markGvlEvidenceRecordVaultCopyAvailable(record)
+function markGvlRevisionEvidenceVaultCopyAvailable(
+ db,
+ snapshotSha256,
+ verification = null
+) {
+ return updateGvlRevisionEvidenceRecords(
+ db,
+ snapshotSha256,
+ (record) => markGvlEvidenceRecordVaultCopyAvailable(record),
+ verification
);
}
@@ -652,7 +666,12 @@ function markGvlRevisionEvidenceProvenance(db, snapshotSha256, provenance) {
);
}
-function updateGvlRevisionEvidenceRecords(db, snapshotSha256, updateRecord) {
+function updateGvlRevisionEvidenceRecords(
+ db,
+ snapshotSha256,
+ updateRecord,
+ verification = null
+) {
return new Promise((resolve, reject) => {
const tx = db.transaction(
[VENDORGET_STORE_NAMES.gvlSnapshots, VENDORGET_STORE_NAMES.gvlRawEvidence],
@@ -665,7 +684,8 @@ function updateGvlRevisionEvidenceRecords(db, snapshotSha256, updateRecord) {
snapshotMarked: false,
rawEvidenceMarked: false,
snapshotSha256,
- rawGvlSha256: null
+ rawGvlSha256: null,
+ skippedReason: null
};
snapshotRequest.onerror = () => reject(snapshotRequest.error);
@@ -673,6 +693,12 @@ function updateGvlRevisionEvidenceRecords(db, snapshotSha256, updateRecord) {
const snapshot = snapshotRequest.result ?? null;
if (!snapshot) {
+ result.skippedReason = "gvl_snapshot_not_found";
+ return;
+ }
+
+ if (!doesGvlRevisionEvidenceMatchVerification(snapshot, verification)) {
+ result.skippedReason = "gvl_revision_evidence_verification_mismatch";
return;
}
@@ -708,6 +734,40 @@ function updateGvlRevisionEvidenceRecords(db, snapshotSha256, updateRecord) {
});
}
+function doesGvlRevisionEvidenceMatchVerification(snapshot, verification) {
+ if (!verification) {
+ return true;
+ }
+
+ if (verification.valid !== true) {
+ return false;
+ }
+
+ if (
+ verification.snapshotSha256 &&
+ snapshot.sha256 !== verification.snapshotSha256
+ ) {
+ return false;
+ }
+
+ if (
+ verification.vendorListVersion !== null &&
+ verification.vendorListVersion !== undefined &&
+ snapshot.vendorListVersion !== verification.vendorListVersion
+ ) {
+ return false;
+ }
+
+ if (
+ verification.rawGvlSha256 &&
+ snapshot.rawGvlSha256 !== verification.rawGvlSha256
+ ) {
+ return false;
+ }
+
+ return true;
+}
+
function formatGvlEvidenceUtcCompact(date) {
return [
date.getUTCFullYear(),
diff --git a/src/dashboard/dashboard.html b/src/dashboard/dashboard.html
index 121a349..b27729f 100644
--- a/src/dashboard/dashboard.html
+++ b/src/dashboard/dashboard.html
@@ -81,15 +81,6 @@
Analyse-Vorbereitung
Datenbestände und vorbereitete Prüffelder, keine Engine.
-
- Datenpflege
-
- Gezielte Verwaltung lokaler Datenbestände. Löschen,
- Wiederherstellen und Exportieren erfolgen künftig segmentbezogen.
- Vorgesehene Segmente sind GVL-Referenzdaten der Browser-DB,
- Consent-Daten, Analyse-Daten und weitere künftige Datenbereiche.
-
-
@@ -127,6 +118,16 @@
+
+
diff --git a/src/data-maintenance/data-maintenance.css b/src/data-maintenance/data-maintenance.css
index 54f738d..7b61bfd 100644
--- a/src/data-maintenance/data-maintenance.css
+++ b/src/data-maintenance/data-maintenance.css
@@ -113,6 +113,10 @@ p {
background: #172033;
}
+.protected-revisions {
+ white-space: pre-line;
+}
+
button {
width: fit-content;
max-width: 100%;
diff --git a/src/data-maintenance/data-maintenance.html b/src/data-maintenance/data-maintenance.html
index b0f8182..581548f 100644
--- a/src/data-maintenance/data-maintenance.html
+++ b/src/data-maintenance/data-maintenance.html
@@ -31,7 +31,7 @@
Consent-Daten, Request-Beobachtungen und Analyse-Daten bleiben
unberührt.
-