Commits vergleichen

...

10 Commits

29 geänderte Dateien mit 3938 neuen und 166 gelöschten Zeilen
@@ -0,0 +1,148 @@
# Background Message Map
Stand: aus der aktuellen Implementierung abgeleitet. Diese Datei beschreibt den beobachteten Ist-Zustand der Background-Ladefolge, Message-Routen und dauerhaften Datenänderungen.
## 1. Background-Lade- und Initialisierungspfad
Die Background-Scripts werden laut `manifest.json` in der folgenden Reihenfolge geladen. Die Dateien laufen nicht als ES-Module, sondern teilen sich den globalen Background-Kontext.
| Reihenfolge | Dateipfad | Aufgabe | Bereitgestellte globale Funktionen / Werte | Unmittelbar erkennbare Abhängigkeiten |
| --- | --- | --- | --- | --- |
| 1 | `src/core/module-registry.js` | Registriert lokale Moduldefinitionen. | `globalThis.VGCoreModuleRegistry.registerModule`, `getModules`, `getModuleById` | keine Projekt-Abhängigkeit |
| 2 | `src/modules/vg-observe/module.js` | Definiert und registriert das VG-Observe-Modul. | `globalThis.VGObserveModule` | `VGCoreModuleRegistry` |
| 3 | `src/background/db/db-constants.js` | Definiert DB-Name, DB-Version und Store-Namen. | `VENDORGET_DB_NAME`, `VENDORGET_DB_VERSION`, `VENDORGET_STORE_NAMES`, `VENDORGET_EVIDENCE_STORE_NAMES` | keine Projekt-Abhängigkeit |
| 4 | `src/background/db/db-core.js` | Öffnet, migriert und löscht die IndexedDB. | `openVendorGetDb`, `closeVendorGetDb`, `deleteVendorGetDatabase`, `ensureGvlStores`, `ensureGvlRelationshipStores` | DB-Konstanten, `indexedDB` |
| 5 | `src/core/evidence-export-json.js` | Exportiert alle Evidence-Stores als JSON-Container. | `exportVendorGetEvidenceJson` | `openVendorGetDb`, `VENDORGET_EVIDENCE_STORE_NAMES` |
| 6 | `src/core/gvl-evidence-json.js` | Export, Verifikation, Import und Provenienzmarkierung von GVL-Evidence. | `exportVendorGetGvlEvidenceJson`, `exportVendorGetGvlRevisionEvidenceJson`, `verifyVendorGetGvlRevisionEvidenceJson`, `importVendorGetGvlRevisionEvidenceJson`, `markVendorGetGvlRevisionEvidenceVaultCopy`, `getGvlEvidenceProvenanceState`, `importVendorGetGvlEvidenceJson`, weitere GVL-Hilfsfunktionen | DB-Konstanten, `openVendorGetDb`, `sha256Hex` erst zur Laufzeit benötigt, `stableStringify`, `VendorGetGvlService` bei Verifikation |
| 7 | `src/background/gvl/gvl-vendor-normalizer.js` | Normalisiert Vendors aus einem GVL-Snapshot. | `globalThis.normalizeGvlVendorsFromSnapshot`, `globalThis.normalizeLatestGvlSnapshotVendors` | `openVendorGetDb`, `VENDORGET_STORE_NAMES` |
| 8 | `src/background/gvl/gvl-vendor-relationship-normalizer.js` | Normalisiert Vendor-Beziehungen zu Purposes, Features und Special Purposes/Features. | `globalThis.normalizeGvlVendorRelationshipsFromSnapshot`, `globalThis.normalizeLatestGvlVendorRelationships` | `openVendorGetDb`, `VENDORGET_STORE_NAMES` |
| 9 | `src/background/gvl/gvl-catalog-normalizer.js` | Normalisiert GVL-Kataloge wie Purposes und Features. | `globalThis.normalizeGvlCatalogsFromSnapshot`, `globalThis.normalizeLatestGvlCatalogs` | `openVendorGetDb`, `VENDORGET_STORE_NAMES` |
| 10 | `src/background/db/db-retention.js` | Zählt Evidence-Daten und löscht ungeschützte, nicht gesperrte Evidence-Records. | `countEvidenceRecords`, `countLockedEvidenceRecords`, `getEvidenceStoreCounts`, `purgeUnlockedEvidenceRecords` | `VENDORGET_EVIDENCE_STORE_NAMES`, `VENDORGET_STORE_NAMES`, `getGvlEvidenceProvenanceState` |
| 11 | `src/background/db/db-record-locks.js` | Setzt oder entfernt Record-Locks auf allen Evidence-Stores. | `lockAllEvidenceRecords`, `unlockAllEvidenceRecords`, `updateAllEvidenceRecords` | `openVendorGetDb`, `VENDORGET_EVIDENCE_STORE_NAMES` |
| 12 | `src/core/stable-serialize.js` | Stabile JSON-Serialisierung mit sortierten Objekt-Keys. | `stableStringify`, `sortObjectKeys` | keine Projekt-Abhängigkeit |
| 13 | `src/background/utils.js` | SHA-256-Hilfsfunktion. | `sha256Hex` | `crypto.subtle`, `TextEncoder` |
| 14 | `src/core/settings-storage.js` | Generische Storage-Helfer. | `getSettingsFromStorage`, `setSettingInStorage` | `browser.storage.local` |
| 15 | `src/background/settings.js` | VG-Observe-Schalter für Consent-Capture und Request-Monitoring. | `getVendorGetSettings`, `setVendorGetSetting`, `isConsentCaptureEnabled`, `isRequestMonitoringEnabled` | Settings-Storage-Helfer |
| 16 | `src/core/maintenance-guard.js` | Kurzlebige Maintenance-Session zum Suspendieren von Evidence-Schreibzugriffen. | `startEvidenceMaintenanceSession`, `refreshEvidenceMaintenanceSession`, `endEvidenceMaintenanceSession`, `isEvidenceWriteSuspended`, `getEvidenceMaintenanceStatus` | Zeitfunktionen |
| 17 | `src/background/consent-memory.js` | Hält zuletzt beobachtete TCF-Pings und Consent-States im Speicher. | `rememberLatestTcfPing`, `getLatestTcfPing`, `rememberLatestConsentState`, `getLatestConsentStateForRequest` | keine persistente Abhängigkeit |
| 18 | `src/background/request-fingerprint.js` | Baut stabile Fingerprint-Quellen für beobachtete Requests. | `buildObservedRequestFingerprintSource`, `buildObservedRequestFingerprint` | `URL`, `sha256Hex`, `stableStringify` |
| 19 | `src/background/request-observer.js` | Beobachtet consentbezogene Requests über `webRequest`. | `handleObservedRequest` | Settings, Maintenance-Guard, Request-Fingerprint, Consent-Memory, `persistObservedRequest` aus `src/background.js` |
| 20 | `src/background/gvl-service.js` | Hashing, Speicherung und Event-Erzeugung für GVL-Rohdaten und Snapshots. | `globalThis.VendorGetGvlService` mit `calculateGvlSnapshotSha256`, `calculateRawGvlSha256`, `storeGvlRawEvidenceIfNew`, `buildGvlSnapshotRecord`, `storeGvlSnapshotIfNew`, `recordGvlSnapshotEvent`, `ingestGvlSnapshot` | `stableStringify`, GVL-Provenienzfunktionen aus `src/core/gvl-evidence-json.js` |
| 21 | `src/core/binary-utils.js` | Bit-Helfer für Base64URL- und TC-String-Decoding. | `base64UrlToBits`, `bitsToInt`, `bitsToBoolean` | `atob` |
| 22 | `src/core/tcf-core-metadata-decoder.js` | Decodiert Core-Metadaten aus TC-Strings. | `decodeTcStringCoreMetadata`, `bitsToString` | Binary-Utils |
| 23 | `src/background.js` | Zentraler Background-Router, Message-Handler, Consent-Persistenz, Request-Persistenz und GVL-Sync. | `handleVendorGetMessage`, Handlerfunktionen, Query-Helfer, `persistConsentState`, `persistObservedRequest` | Alle zuvor geladenen Komponenten, `browser.runtime.onMessage`, `browser.webRequest.onBeforeRequest`, `fetch` |
Bei Initialisierung registriert `src/background.js` `browser.runtime.onMessage` auf `handleVendorGetMessage` und `browser.webRequest.onBeforeRequest` auf `handleObservedRequest`. Der automatische GVL-Update-Pfad ist im aktuellen Code nicht aktiv gestartet; es wird lediglich geloggt: `GVL auto update disabled; use manual sync`.
## 2. Message-Katalog
| Message-Name | Zuständiger Handler | Zweck | Art des Zugriffs | Fachbereiche |
| --- | --- | --- | --- | --- |
| `export_gvl_evidence_json` | `handleExportGvlEvidenceJsonMessage` | Exportiert alle GVL-Evidence-Stores. | Lesen | GVL, Export |
| `export_gvl_revision_evidence_json` | `handleExportGvlRevisionEvidenceJsonMessage` | Exportiert eine einzelne GVL-Revision inklusive Raw Evidence, Snapshot und normalisierten Records. | Lesen | GVL, Export |
| `verify_gvl_revision_evidence_json` | `handleVerifyGvlRevisionEvidenceJsonMessage` | Prüft ein GVL-Revision-Evidence-Paket gegen Hashes, Metadaten und Konsistenzregeln. | Lesen | GVL, Import, Export |
| `import_gvl_revision_evidence_json` | `handleImportGvlRevisionEvidenceJsonMessage` | Importiert eine verifizierte GVL-Revision aus einem Evidence-Paket. | Lesen und Schreiben | GVL, Import |
| `mark_gvl_revision_evidence_vault_copy` | `handleMarkGvlRevisionEvidenceVaultCopyMessage` | Markiert Snapshot und Raw-GVL einer Revision als Vault-Kopie verfügbar. | Lesen und Schreiben | GVL, Export |
| `import_gvl_evidence_json` | `handleImportGvlEvidenceJsonMessage` | Importiert einen älteren/gesamten GVL-Evidence-Exportcontainer. | Lesen und Schreiben | GVL, Import |
| `gvl_import_json` | `handleGvlImportJsonMessage` | Importiert eine lokale GVL-JSON als Snapshot über die GVL-Service-Pipeline. | Lesen und Schreiben | GVL, Import |
| `fetch_official_gvl` | `handleFetchOfficialGvlMessage` | Ruft die offizielle IAB-GVL ab, speichert Raw Evidence/Snapshot und normalisiert Daten. | Lesen und Schreiben | GVL |
| `export_evidence_json` | `handleExportEvidenceJsonMessage` | Exportiert alle Evidence-Stores. | Lesen | Consent, Requests, GVL, Export |
| `start_evidence_maintenance_session` | `startEvidenceMaintenanceSession` | Startet eine kurzlebige In-Memory-Maintenance-Session. | Schreiben | Settings, Purge |
| `refresh_evidence_maintenance_session` | `refreshEvidenceMaintenanceSession` | Erneuert die Maintenance-Session. | Schreiben | Settings, Purge |
| `end_evidence_maintenance_session` | `endEvidenceMaintenanceSession` | Beendet die Maintenance-Session für die angegebene Quelle. | Schreiben | Settings, Purge |
| `get_evidence_maintenance_status` | `getEvidenceMaintenanceStatus` | Meldet, ob Evidence-Schreibzugriffe suspendiert sind. | Lesen | Settings, Dashboard |
| `get_evidence_retention_status` | `handleGetEvidenceRetentionStatusMessage` | Zählt Evidence-Records, gesperrte Records und Store-Bestände. | Lesen | Consent, Requests, GVL, Purge, Dashboard |
| `get_latest_gvl_update_status` | `handleGetLatestGvlUpdateStatusMessage` | Liefert den letzten bekannten GVL-Update-Status und lokale Snapshot-Infos. | Lesen | GVL, Dashboard |
| `list_gvl_snapshots` | `handleListGvlSnapshotsMessage` | Listet aktuelle GVL-Snapshots mit Event- und Provenienzstatus. | Lesen | GVL, Dashboard |
| `get_gvl_snapshot_summary` | `handleGetGvlSnapshotSummaryMessage` | Liefert Summary, Zählungen und Diagnostik für einen GVL-Snapshot. | Lesen | GVL, Dashboard |
| `rebuild_gvl_snapshot_normalized_data` | `handleRebuildGvlSnapshotNormalizedDataMessage` | Baut normalisierte GVL-Daten für einen vorhandenen Snapshot neu auf. | Lesen und Schreiben | GVL |
| `list_gvl_vendors_for_snapshot` | `handleListGvlVendorsForSnapshotMessage` | Listet normalisierte Vendoren eines Snapshots. | Lesen | GVL, Dashboard |
| `get_gvl_vendor_detail` | `handleGetGvlVendorDetailMessage` | Liefert Vendor-Detaildaten inklusive Beziehungen, Katalogtexten, Snapshot und Raw-Evidence-Summary. | Lesen | GVL, Dashboard |
| `get_latest_consent_state` | `handleGetLatestConsentStateMessage` | Liefert den zuletzt gesehenen Consent-State. | Lesen | Consent, Dashboard |
| `list_recent_consent_states` | `handleListRecentConsentStatesMessage` | Listet die letzten Consent-States. | Lesen | Consent, Dashboard |
| `list_recent_observed_requests` | `handleListRecentObservedRequestsMessage` | Listet zuletzt beobachtete Requests. | Lesen | Requests, Dashboard |
| `purge_unlocked_evidence_records` | `handlePurgeUnlockedEvidenceRecordsMessage` | Löscht nicht gesperrte und nicht workspace-geschützte Evidence-Records. | Lesen und Schreiben | Consent, Requests, GVL, Purge |
| `delete_all_evidence_database` | `handleDeleteAllEvidenceDatabaseMessage` | Löscht die komplette IndexedDB. | Schreiben | Consent, Requests, GVL, Purge |
| `lock_all_evidence_records` | `lockAllEvidenceRecords` | Setzt Lock-Metadaten auf allen Evidence-Records. | Lesen und Schreiben | Consent, Requests, GVL, Purge |
| `unlock_all_evidence_records` | `unlockAllEvidenceRecords` | Entfernt Lock-Metadaten auf allen Evidence-Records. | Lesen und Schreiben | Consent, Requests, GVL, Purge |
| `vendorget_capture` mit `eventName: "tcf_ping"` | `handleVendorGetMessage` | Speichert den letzten TCF-Ping pro Tab nur im Background-Speicher. | Schreiben | Consent |
| `vendorget_capture` mit `eventName: "consent_capture"` | `handleVendorGetMessage` und `persistConsentState` | Baut, fingerprintet und persistiert einen Consent-State sowie ein Consent-Event. | Lesen und Schreiben | Consent |
Zusätzlich existiert kein Runtime-Message-Typ für `setVendorGetSetting`; die Popup-UI ruft diese Funktion wegen gemeinsamer Script-Einbindung direkt aus dem Popup-Kontext auf.
## 3. Dauerhafte Datenänderungen
| Funktion | Betroffene IndexedDB-Stores / Storage | Zweck der Änderung | Bezug zu Evidence-Daten | Besondere Sensibilität |
| --- | --- | --- | --- | --- |
| `openVendorGetDb` / `ensureGvlStores` / `ensureGvlRelationshipStores` | alle IndexedDB-Stores bei Migration/Upgrade | Additive Anlage von Object Stores und Indizes. | Legt die Speicherstruktur für Evidence und GVL-Daten fest. | Sehr hoch: DB-Version, KeyPaths und Indizes bestimmen Lesbarkeit und Migration vorhandener Evidence. |
| `deleteVendorGetDatabase` | gesamte IndexedDB `vendorget_iv` | Löscht die komplette lokale Datenbank. | Entfernt alle lokalen Evidence- und GVL-Daten. | Extrem hoch: vollständiger Datenverlust. |
| `persistConsentState` | `consent_states`, `consent_events` | Fügt neue Consent-States hinzu oder aktualisiert bestehende Duplikate; schreibt immer ein Consent-Event. | Kern-Evidence für beobachtete Consent-Zustände. | Sehr hoch: Fingerprint, Zeitstempel, Raw-TC-Daten und Eventhistorie beeinflussen Nachvollziehbarkeit. |
| `persistObservedRequest` | `observed_requests` | Fügt beobachtete consentbezogene Requests hinzu oder aktualisiert Wiederholungen. | Request-Evidence und Korrelation zu Consent-States. | Hoch: Fingerprint-Quelle, Seen-Zählung und Korrelation beeinflussen Rekonstruktion. |
| `VendorGetGvlService.storeGvlRawEvidenceIfNew` | `gvl_raw_evidence` | Speichert Raw-GVL oder markiert vorhandene Raw-GVL mit Web-Provenienz. | Raw Evidence für GVL-Inhalte. | Sehr hoch: Raw Body und Hashes sind Grundlage späterer Verifikation. |
| `VendorGetGvlService.storeGvlSnapshotIfNew` | `gvl_snapshots` | Speichert Snapshot oder ergänzt Web-Provenienz an vorhandenem Snapshot. | GVL-Snapshot-Evidence. | Sehr hoch: Snapshot-Hash, Vendorlisten-Version und Provenienz steuern Export/Purge. |
| `VendorGetGvlService.recordGvlSnapshotEvent` | `gvl_snapshot_events` | Schreibt Ereignisse zu GVL-Ingest, Auto-Update, Fehlern oder Status. | Event-Evidence zum lokalen GVL-Verlauf. | Hoch: Ereignishistorie erklärt Entstehungskontext. |
| `VendorGetGvlService.ingestGvlSnapshot` | `gvl_snapshots`, `gvl_snapshot_events` | Speichert Snapshot und erzeugt Event `gvl_snapshot_ingested` oder `gvl_snapshot_already_known`. | GVL-Evidence und lokale Beobachtungshistorie. | Sehr hoch: Erzeugt zusätzliche Evidence zum Ingest-Pfad. |
| `importVendorGetGvlRevisionEvidenceJson` / `importGvlRevisionEvidenceStores` | `gvl_raw_evidence`, `gvl_snapshots`, normalisierte GVL-Stores | Importiert verifizierte GVL-Revisionsdaten; bestehende Raw/Snapshot-Records können Provenienz per `put` erhalten. | Rekonstruiert GVL-Evidence aus Vault-Paketen. | Sehr hoch: Import validiert und entscheidet zwischen Insert, Skip und Provenienzmerge. |
| `importVendorGetGvlEvidenceJson` / `importGvlEvidenceStores` | `gvl_raw_evidence`, `gvl_snapshots`, normalisierte GVL-Stores | Importiert GVL-Evidence-Store-Arrays und überspringt vorhandene Keys. | GVL-Evidence-Import. | Hoch: Key-Ermittlung und Skip-Logik bestimmen Dublettenverhalten. |
| `markVendorGetGvlRevisionEvidenceVaultCopy` / `markGvlRevisionEvidenceVaultCopyAvailable` / `updateGvlRevisionEvidenceRecords` | `gvl_snapshots`, `gvl_raw_evidence` | Markiert Vault-Kopie und Workspace-Löschbarkeit für Snapshot und Raw-GVL. | Verändert Evidence-Provenienz und Schutzstatus. | Sehr hoch: beeinflusst Purge-Schutz und Audit-Aussage zur Vault-Verfügbarkeit. |
| `markGvlRevisionEvidenceWebSource` / `markGvlRevisionEvidenceProvenance` | `gvl_snapshots`, `gvl_raw_evidence` | Ergänzt Web-Provenienz an GVL-Revisionsrecords. | Verändert GVL-Provenienz. | Hoch: Provenienz beeinflusst Schutzstatus und Interpretation der Herkunft. |
| `putGvlVendorRecords` | `gvl_vendors` | Schreibt normalisierte Vendor-Records per `put`. | Normalisierte Ableitung aus GVL-Snapshot. | Hoch: überschreibt normalisierte Sicht auf Vendor-Daten einer Revision. |
| `putGvlVendorRelationshipRecords` | `gvl_vendor_relationships` | Schreibt normalisierte Vendor-Beziehungsrecords per `put`. | Normalisierte Ableitung aus GVL-Snapshot. | Hoch: beeinflusst Vendor-Detail- und Purpose/Feature-Nachvollziehbarkeit. |
| `putGvlCatalogRecords` | `gvl_purposes`, `gvl_special_purposes`, `gvl_features`, `gvl_special_features` | Schreibt normalisierte Katalogrecords per `put`. | Normalisierte GVL-Katalogableitung. | Hoch: Katalogtexte und IDs sind Grundlage der Darstellung. |
| `normalizeGvlSnapshotWithExistingPipeline` | normalisierte GVL-Stores | Führt Vendor-, Katalog- und Relationship-Normalisierung aus. | Erstellt/aktualisiert normalisierte Evidence-Ableitungen. | Hoch: gemeinsamer Einstieg für Rebuild und GVL-Fetch. |
| `purgeUnlockedEvidenceRecords` | alle Stores in `VENDORGET_EVIDENCE_STORE_NAMES` | Löscht nicht gelockte und nicht workspace-geschützte Records per Cursor. | Entfernt lokale Evidence-Records selektiv. | Extrem hoch: Löschlogik hängt von Locks und GVL-Provenienzschutz ab. |
| `lockAllEvidenceRecords` / `updateAllEvidenceRecords` | alle Stores in `VENDORGET_EVIDENCE_STORE_NAMES` | Setzt `bolRecordLock` und Lock-Metadaten auf allen Evidence-Records. | Schützt Evidence vor Purge. | Sehr hoch: Vollständiger Record-Durchlauf; Schutzstatus wird verändert. |
| `unlockAllEvidenceRecords` / `updateAllEvidenceRecords` | alle Stores in `VENDORGET_EVIDENCE_STORE_NAMES` | Entfernt Lock-Metadaten und setzt Unlock-Zeit. | Macht Evidence wieder purgefähig, sofern kein anderer Schutz greift. | Sehr hoch: verändert Schutzstatus aller Evidence-Records. |
| `storeAutoGvlCheckThrottleState` | `browser.storage.local` unter `vendorgetAutoGvlUpdateStatus` | Speichert Auto-GVL-Throttle-Status. | Kein IndexedDB-Evidence-Record, aber Status zur GVL-Automatik. | Mittel: beeinflusst Statusmeldungen und Auto-Check-Verhalten, aktuell nicht gestartet. |
| `setSettingInStorage` / `setVendorGetSetting` | `browser.storage.local` unter `vendorgetSettings` | Speichert Consent-Capture- und Request-Monitoring-Schalter. | Steuert, ob neue Evidence geschrieben wird. | Hoch: Schalter bestimmen Beobachtungsumfang. |
| `startEvidenceMaintenanceSession` / `refreshEvidenceMaintenanceSession` / `endEvidenceMaintenanceSession` | nur In-Memory | Setzt/erneuert/beendet temporäre Schreibsperre. | Verhindert während Maintenance neue Evidence-Schreibzugriffe. | Mittel bis hoch: nicht persistent, aber direkt wirksam auf Capture/Request-Writes. |
Hinweis zu Store-Abdeckung: `VENDORGET_EVIDENCE_STORE_NAMES` enthält `consent_states`, `consent_events`, `observed_requests`, `gvl_snapshots`, `gvl_snapshot_events`, `gvl_vendors`, `gvl_purposes`, `gvl_special_purposes`, `gvl_features`, `gvl_special_features`, `gvl_data_categories` und `gvl_vendor_relationships`. `gvl_raw_evidence` ist nicht in dieser allgemeinen Evidence-Store-Liste enthalten, wird aber in den GVL-spezifischen Evidence-Import/Export-Pfaden verwendet.
## 4. Sensible Bereiche
| Bereich | Betroffene Komponenten | Grund der Sensibilität | Mögliche Auswirkungen von Änderungen |
| --- | --- | --- | --- |
| IndexedDB-Schema und Store-Konstanten | `db-constants.js`, `db-core.js`, alle DB-Nutzer | KeyPaths, Store-Namen, Indizes und DB-Version sind Grundlage aller Evidence-Zugriffe. | Bestehende Evidence kann unlesbar, nicht migrierbar oder falsch gezählt werden. |
| Consent-Capture-Persistenz | `background.js`, `tcf-core-metadata-decoder.js`, `stable-serialize.js`, `utils.js`, `consent-memory.js` | `stateFingerprint`, `rawTcData`, Zeitstempel und Eventtypen bilden die technische Consent-Evidence. | Duplikaterkennung, Rekonstruktion und Audit-Trail können verfälscht werden. |
| Request-Beobachtung und Korrelation | `request-observer.js`, `request-fingerprint.js`, `consent-memory.js`, `background.js` | Requests werden dedupliziert, fingerprinted und optional mit dem letzten Consent-State korreliert. | Request-Evidence kann fehlen, zusammenfallen oder falschen Consent-Zuständen zugeordnet werden. |
| GVL-Raw/Snapshot-Hashing | `gvl-service.js`, `gvl-evidence-json.js`, `stable-serialize.js` | Hashes sind Grundlage für Snapshot-Identität, Raw-Evidence-Verifikation und Exportpakete. | GVL-Revisionen könnten nicht mehr vergleichbar oder verifizierbar sein. |
| GVL-Provenienz und Vault-Markierung | `gvl-evidence-json.js`, `db-retention.js`, `gvl-explorer.js` | `gvlEvidenceProvenance`, `vaultCopyAvailable` und `evidenceWorkspaceDeleteAllowed` steuern Schutzstatus und Exportstatus. | Purge kann zu viel oder zu wenig löschen; Vault-Aussagen können unzuverlässig werden. |
| GVL-Normalisierung | `gvl-vendor-normalizer.js`, `gvl-vendor-relationship-normalizer.js`, `gvl-catalog-normalizer.js`, `background.js` | Normalisierte Stores sind abgeleitete Sicht auf eine konkrete GVL-Revision. | Vendor-Details, Katalogtexte und Zählungen können von Raw-Snapshot abweichen. |
| Purge- und Lock-Logik | `db-retention.js`, `db-record-locks.js`, `background.js`, Popup/Data-Maintenance | Diese Pfade löschen oder schützen Evidence über viele Stores hinweg. | Datenverlust, ungewollte Schutzumgehung oder falsche Retention-Summaries. |
| Export/Import-Pakete | `evidence-export-json.js`, `gvl-evidence-json.js`, Popup/GVL-Explorer | Exportformate bilden auditierbare Übergabecontainer. | Spätere Reimporte, Payload-Hashes und Nachvollziehbarkeit können brechen. |
| Maintenance-Guard | `maintenance-guard.js`, `background.js`, `request-observer.js` | Temporäre Schreibsperre verhindert parallele Evidence-Writes während Wartung. | Während Wartung können Daten fehlen oder trotz Wartung geschrieben werden. |
| Settings | `settings-storage.js`, `settings.js`, `popup.js` | Browser-Storage-Schalter steuern Consent-Capture und Request-Monitoring. | Beobachtungsumfang und Evidence-Erzeugung ändern sich ohne DB-Schemaänderung. |
## 5. Überwiegend lesende Bereiche
| Komponente | Aufgabe | Verwendete Datenquellen | Art des Zugriffs |
| --- | --- | --- | --- |
| `src/core/evidence-export-json.js` | Baut einen vollständigen Evidence-Exportcontainer. | `VENDORGET_EVIDENCE_STORE_NAMES` über IndexedDB-Cursor | Lesen |
| `exportVendorGetGvlEvidenceJson` / `readGvlEvidenceStores` in `src/core/gvl-evidence-json.js` | Exportiert GVL-Evidence-Stores. | GVL-Evidence-Stores über IndexedDB-Cursor | Lesen |
| `exportVendorGetGvlRevisionEvidenceJson` in `src/core/gvl-evidence-json.js` | Exportiert eine einzelne GVL-Revision. | `gvl_snapshots`, `gvl_raw_evidence`, normalisierte GVL-Stores | Lesen |
| `verifyVendorGetGvlRevisionEvidenceJson` in `src/core/gvl-evidence-json.js` | Prüft einen übergebenen Exportcontainer. | Import-/Exportobjekt im Speicher, Hashfunktionen | Lesen |
| `handleGetEvidenceRetentionStatusMessage` und Zählfunktionen in `db-retention.js` | Liefert Gesamt-, Lock- und Store-Zählungen. | Evidence-Stores | Lesen |
| `handleGetLatestGvlUpdateStatusMessage` | Liefert lokalen GVL-Status und letzten In-Memory-Update-Status. | `gvl_snapshots`, Browser-Storage-Throttle, In-Memory-Status | Lesen |
| `handleListGvlSnapshotsMessage` | Listet GVL-Snapshots mit Event- und Provenienzstatus. | `gvl_snapshots`, `gvl_snapshot_events` | Lesen |
| `handleGetGvlSnapshotSummaryMessage` | Baut Snapshot-Summary mit normalisierten Zählungen. | `gvl_snapshots`, `gvl_snapshot_events`, normalisierte GVL-Stores | Lesen |
| `handleListGvlVendorsForSnapshotMessage` | Listet Vendoren einer Revision. | `gvl_vendors`, `gvl_snapshots` | Lesen |
| `handleGetGvlVendorDetailMessage` | Baut Vendor-Detailansicht. | `gvl_vendors`, `gvl_vendor_relationships`, Katalogstores, `gvl_snapshots`, `gvl_raw_evidence` | Lesen |
| `handleGetLatestConsentStateMessage` | Liefert neuesten Consent-State. | `consent_states` über `lastSeenAt` | Lesen |
| `handleListRecentConsentStatesMessage` | Listet aktuelle Consent-States. | `consent_states` über `lastSeenAt` | Lesen |
| `handleListRecentObservedRequestsMessage` | Listet aktuelle beobachtete Requests. | `observed_requests` über `lastSeenAt` | Lesen |
| `src/dashboard/dashboard.js` | Zeigt Evidence-Bestand und GVL-Status. | Runtime-Messages `get_evidence_retention_status`, `get_latest_gvl_update_status` | Lesen |
| `src/analysis-dashboard/analysis-dashboard.js` | Zeigt zusammenfassende Bestände und Status. | Runtime-Messages `get_evidence_retention_status`, `get_latest_gvl_update_status` | Lesen |
| `src/consent-explorer/consent-explorer.js` | Stellt Consent-States dar. | Runtime-Message `list_recent_consent_states` | Lesen |
| `src/request-explorer/request-explorer.js` | Stellt beobachtete Requests dar. | Runtime-Message `list_recent_observed_requests` | Lesen |
| `src/gvl-explorer/gvl-explorer.js` | Stellt GVL-Snapshots, Vendoren und Revisionsexporte dar; enthält zusätzlich schreibende Aktionen für Fetch, Import, Rebuild und Vault-Markierung. | GVL-Runtime-Messages | Überwiegend Lesen, einzelne Schreiben |
## 6. Audit-Hinweis
Besonders kritisch sind DB-Schema, Consent-Persistenz, Request-Fingerprints, GVL-Hashing/Provenienz, Import/Export, Purge und Record-Locks.
Überwiegend lesend arbeiten Export-, Dashboard-, Explorer- und Summary-Pfade, solange sie keine Import-, Fetch-, Rebuild-, Markierungs- oder Purge-Aktion auslösen.
Bei späteren Änderungen verdienen `src/background.js`, `src/background/db/db-core.js`, `src/background/db/db-retention.js`, `src/background/db/db-record-locks.js`, `src/core/gvl-evidence-json.js` und `src/background/gvl-service.js` besondere Aufmerksamkeit.
Änderungen an Settings und Maintenance-Guard sind ebenfalls auditrelevant, weil sie steuern, ob Evidence überhaupt geschrieben wird.
+2
Datei anzeigen
@@ -1,5 +1,7 @@
# VG-Environment Codex Workflow # VG-Environment Codex Workflow
Vor Beginn fachlicher Arbeiten ist `docs/architecture/project-philosophy.md` als maßgebliche Beschreibung des Projektkerns zu berücksichtigen.
## Grundprinzip ## Grundprinzip
VG-Environment wird schrittweise modularisiert. VG-Environment wird schrittweise modularisiert.
+28
Datei anzeigen
@@ -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.
+103
Datei anzeigen
@@ -0,0 +1,103 @@
# VG-Environment – Wesen und Kern des Projekts
VG-Environment ist kein GVL-Explorer.
VG-Environment ist kein Dashboard.
VG-Environment ist keine Analyse-Engine.
VG-Environment ist ein lokales, evidenzorientiertes Rekonstruktionswerkzeug zur Untersuchung der technisch beobachtbaren Wirkungen von Consent-Entscheidungen.
Die zentrale Frage des Projekts lautet:
> Was ist tatsächlich geschehen, nachdem ich dieser konkreten Entscheidung zugestimmt habe?
Zur Beantwortung dieser Frage trennt VG-Environment bewusst drei unterschiedliche Wirklichkeitsebenen.
## 1. Die Provider-Erzählung
Ein CMP präsentiert dem Nutzer eine Erklärung.
Der Nutzer trifft eine Entscheidung.
Aus dieser Entscheidung entsteht ein tcString.
Der tcString dokumentiert nicht die technische Realität. Er dokumentiert die Behauptung des Systems darüber, welche Zustimmungen, Ablehnungen und Berechtigungen vorliegen sollen.
Er ist die digitale Selbstbeschreibung des Consent-Vorgangs.
VG-Environment behandelt diese Beschreibung als Evidenzobjekt – nicht als Wahrheit.
## 2. Die technische Realität
Unabhängig von jeder Erklärung werden technische Folgehandlungen beobachtet.
Requests finden statt oder finden nicht statt.
Empfänger werden kontaktiert oder werden nicht kontaktiert.
Parameter werden übertragen oder werden nicht übertragen.
Diese Beobachtungen stellen die stärkste Form der Primärevidence des Projekts dar.
Sie benötigen keine GVL und keine Interpretation, um zu existieren.
Sie sind die dokumentierte technische Realität.
## 3. Das Regelwerk
Die GVL beschreibt nicht die technische Realität.
Sie erklärt auch nicht die Requests.
Sie beschreibt vielmehr, wie die Behauptungen des Providers innerhalb des TCF-Regelwerks einzuordnen gewesen wären.
Sie beantwortet Fragen wie:
- Welche Zwecke hatte ein Vendor angegeben?
- Welche Eigenschaften waren einem Vendor zugeordnet?
- Welche Beziehungen waren innerhalb des Regelwerks vorgesehen?
Die GVL ist daher Referenz- und Analysematerial.
Sie ist ein historisches Wörterbuch zur Bewertung der Provider-Erzählung.
Sie ist nicht Bestandteil des beobachteten Vorgangs selbst.
## Die eigentliche Rekonstruktion
VG-Environment bringt diese drei Ebenen nicht durcheinander.
Es hält sie bewusst getrennt.
Erst aus ihrer Gegenüberstellung entstehen die entscheidenden Fragen:
- Was wurde behauptet?
- Was ist tatsächlich geschehen?
- Entspricht die Behauptung dem eigenen Regelwerk?
Die Antworten darauf entstehen nicht durch versteckte Bewertungen, sondern durch nachvollziehbare, auditierbare Evidence-Ketten.
## Das Leitprinzip
VG-Environment entscheidet nicht, was richtig oder falsch ist.
VG-Environment dokumentiert.
Es rekonstruiert.
Es macht technische Realität sichtbar.
Es zeigt Herkunft, Zusammenhänge und Widersprüche transparent auf.
Die Bewertung bleibt dem Nutzer, Forschenden, Journalisten, Aufsichtsbehörden oder Gerichten überlassen.
## Der Kern des Projekts
> VG-Environment ist ein Werkzeug zur Rekonstruktion technischer Realität im Spannungsfeld zwischen Provider-Erzählung, beobachtbarer Handlung und Regelwerk.
Oder kürzer:
> Es zeigt nicht, was hätte geschehen sollen.
>
> Es zeigt, was behauptet wurde, was tatsächlich geschah und auf welcher Grundlage beides beurteilt werden kann.
@@ -1,5 +1,8 @@
# VG-Environment — Project Principles # VG-Environment — Project Principles
Hinweis:
Die grundlegende Zieldefinition und das Erkenntnisinteresse von VG-Environment sind in `docs/architecture/project-philosophy.md` beschrieben und bei allen Architektur-, Implementierungs- und Analyseentscheidungen vorrangig zu berücksichtigen.
## Core purpose ## Core purpose
VG-Environment is a local evidence and runtime reconstruction environment for consent-related browser activity. VG-Environment is a local evidence and runtime reconstruction environment for consent-related browser activity.
+1
Datei anzeigen
@@ -40,6 +40,7 @@
"src/background/gvl-service.js", "src/background/gvl-service.js",
"src/core/binary-utils.js", "src/core/binary-utils.js",
"src/core/tcf-core-metadata-decoder.js", "src/core/tcf-core-metadata-decoder.js",
"src/core/consent-diff.js",
"src/background.js" "src/background.js"
] ]
}, },
@@ -3,19 +3,18 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>VG-Observe Analyse-Dashboard</title> <title>VG-Observe Analyse-Vorbereitung</title>
<link rel="stylesheet" href="analysis-dashboard.css"> <link rel="stylesheet" href="analysis-dashboard.css">
</head> </head>
<body> <body>
<main class="analysis-dashboard"> <main class="analysis-dashboard">
<header class="analysis-header"> <header class="analysis-header">
<a class="back-link" href="../dashboard/dashboard.html">Zurück zum Dashboard</a> <a class="back-link" href="../dashboard/dashboard.html">Zurück zum Observe-Dashboard</a>
<h1>Analyse-Dashboard</h1> <h1>Analyse-Vorbereitung</h1>
<p> <p>
Diese Ansicht bereitet technische Prüfungen zwischen Diese Ansicht ist noch keine Analyse-Engine. Sie zeigt vorhandene
Consent-Zuständen, Vendorlisten und beobachteten Requests vor. Datenbestände und vorbereitet strukturierte Prüffelder für spätere
Aktuell werden nur vorhandene Datenbestände und vorbereitete Auswertung, ohne Bewertung oder Zuordnung zu berechnen.
Analysebereiche angezeigt.
</p> </p>
<div id="analysis-status" class="analysis-status" aria-live="polite"> <div id="analysis-status" class="analysis-status" aria-live="polite">
Lade Datenbestände Lade Datenbestände
@@ -34,11 +33,11 @@
<dd id="summary-observed-requests">-</dd> <dd id="summary-observed-requests">-</dd>
</div> </div>
<div> <div>
<dt>Lokal gespeicherte Vendorlisten</dt> <dt>Lokal gespeicherte GVL-Referenzen</dt>
<dd id="summary-gvl-snapshots">-</dd> <dd id="summary-gvl-snapshots">-</dd>
</div> </div>
<div> <div>
<dt>Lokal aktuelle Vendorlisten-Version</dt> <dt>Lokal aktuelle GVL-Referenzversion</dt>
<dd id="summary-current-gvl-version">-</dd> <dd id="summary-current-gvl-version">-</dd>
</div> </div>
<div> <div>
@@ -49,23 +48,23 @@
</section> </section>
<section class="panel" aria-labelledby="areas-title"> <section class="panel" aria-labelledby="areas-title">
<h2 id="areas-title">Vorbereitete Analysebereiche</h2> <h2 id="areas-title">Vorbereitete Prüffelder</h2>
<div class="area-grid"> <div class="area-grid">
<article> <article>
<h3>Consent ↔ Vendorliste</h3> <h3>Consent ↔ Vendorliste</h3>
<p>Analyse noch nicht ausgeführt.</p> <p>Vorbereitung vorhanden, keine Analyse ausgeführt.</p>
</article> </article>
<article> <article>
<h3>Consent ↔ beobachtete Requests</h3> <h3>Consent ↔ beobachtete Requests</h3>
<p>Analyse noch nicht ausgeführt.</p> <p>Vorbereitung vorhanden, keine Analyse ausgeführt.</p>
</article> </article>
<article> <article>
<h3>Request-Hosts ↔ bekannte Vendoren</h3> <h3>Request-Hosts ↔ bekannte Vendoren</h3>
<p>Keine erkennbare Zuordnung berechnet. Analyse noch nicht ausgeführt.</p> <p>Keine Zuordnung berechnet. Vorbereitung vorhanden, keine Analyse ausgeführt.</p>
</article> </article>
<article> <article>
<h3>Potenziell erklärungsbedürftige technische Diskrepanzen</h3> <h3>Potenziell erklärungsbedürftige technische Diskrepanzen</h3>
<p>Keine Bewertung vorgenommen. Analyse noch nicht ausgeführt.</p> <p>Keine Bewertung vorgenommen. Vorbereitung vorhanden, keine Analyse ausgeführt.</p>
</article> </article>
</div> </div>
</section> </section>
+1070 -1
Datei anzeigen
Datei-Diff unterdrückt, da er zu groß ist Diff laden
+6
Datei anzeigen
@@ -89,6 +89,12 @@ async function purgeUnlockedEvidenceRecords(db) {
}); });
} }
// 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) { function buildGvlWorkspaceProtectionIndex(db) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const protectedSnapshotSha256 = new Set(); const protectedSnapshotSha256 = new Set();
+173
Datei anzeigen
@@ -27,6 +27,7 @@ body {
h1, h1,
h2, h2,
h3, h3,
h4,
p { p {
margin: 0; margin: 0;
} }
@@ -49,6 +50,11 @@ h3 {
color: #cbd5e1; color: #cbd5e1;
} }
h4 {
font-size: 12px;
color: #cbd5e1;
}
p { p {
max-width: 760px; max-width: 760px;
font-size: 13px; font-size: 13px;
@@ -139,6 +145,165 @@ th {
margin-top: 18px; margin-top: 18px;
} }
.finding-overview {
display: grid;
gap: 10px;
margin-bottom: 16px;
}
.finding-note {
max-width: 900px;
}
.finding-list {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1px;
margin: 0;
overflow: hidden;
border: 1px solid #334155;
border-radius: 4px;
background: #334155;
font-size: 13px;
}
.finding-list div {
display: grid;
grid-template-columns: minmax(160px, 220px) 1fr;
gap: 10px;
min-width: 0;
padding: 10px 12px;
background: #1f2937;
}
.finding-list dt {
color: #cbd5e1;
font-weight: 700;
}
.finding-list dd {
margin: 0;
overflow-wrap: anywhere;
color: #e5edf5;
}
.consent-diff {
display: grid;
gap: 10px;
margin-top: 10px;
padding: 10px;
border: 1px solid #334155;
border-radius: 4px;
background: #172033;
}
.compact-definition-list {
display: grid;
gap: 6px;
margin: 0;
font-size: 12px;
}
.compact-definition-list div {
display: grid;
grid-template-columns: minmax(150px, 220px) 1fr;
gap: 10px;
min-width: 0;
}
.compact-definition-list dt {
color: #cbd5e1;
font-weight: 700;
}
.compact-definition-list dd {
margin: 0;
overflow-wrap: anywhere;
}
.diff-note {
max-width: none;
font-size: 12px;
color: #cbd5e1;
}
.consent-diff-primary {
display: grid;
gap: 8px;
}
.consent-diff-findings {
display: grid;
gap: 6px;
margin: 0;
padding: 0;
list-style: none;
}
.consent-diff-findings li {
padding: 9px 10px;
border: 1px solid #334155;
border-radius: 4px;
color: #e5edf5;
background: #1f2937;
overflow-wrap: anywhere;
}
.consent-diff-finding-details summary {
cursor: pointer;
font-weight: 700;
}
.consent-diff-finding-body {
display: grid;
gap: 8px;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #334155;
}
.consent-change-categories,
.consent-change-category-body {
display: grid;
gap: 10px;
}
.consent-change-category {
padding: 8px 10px;
border: 1px solid #334155;
border-radius: 4px;
background: #172033;
}
.consent-change-category summary,
.consent-change-collapsed-list summary {
cursor: pointer;
font-weight: 700;
}
.consent-change-list-block {
display: grid;
gap: 6px;
}
.inline-id-list {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: 0;
padding: 0;
list-style: none;
}
.inline-id-list li {
padding: 3px 6px;
border: 1px solid #334155;
border-radius: 3px;
background: #0f172a;
font-size: 12px;
color: #e5edf5;
}
.inspector-table th, .inspector-table th,
.inspector-table td { .inspector-table td {
vertical-align: top; vertical-align: top;
@@ -227,4 +392,12 @@ th {
.inspector-table .inspector-explanation { .inspector-table .inspector-explanation {
width: auto; width: auto;
} }
.finding-list {
grid-template-columns: 1fr;
}
.finding-list div {
grid-template-columns: 1fr;
}
} }
+8 -7
Datei anzeigen
@@ -3,23 +3,23 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>VG-Observe Consent-Explorer</title> <title>VG-Observe Consent untersuchen</title>
<link rel="stylesheet" href="consent-explorer.css"> <link rel="stylesheet" href="consent-explorer.css">
</head> </head>
<body> <body>
<main class="explorer"> <main class="explorer">
<header class="explorer-header"> <header class="explorer-header">
<a class="back-link" href="../dashboard/dashboard.html">Zurück zum Dashboard</a> <a class="back-link" href="../dashboard/dashboard.html">Zurück zum Observe-Dashboard</a>
<h1>Dokumentierte Consent-Zustände</h1> <h1>Consent untersuchen</h1>
<p class="section-help"> <p class="section-help">
Diese Ansicht zeigt gespeicherte Consent-Zustände aus der lokalen Consent ist das zentrale Untersuchungsobjekt von VG-Observe. Diese
Beobachtungsdatenbank. Jeder Eintrag ist ein dokumentierter Zustand, Ansicht zeigt lokal beobachtete Consent-Zustände und ihre technischen
den VG-Observe während der Browser-Laufzeit beobachtet hat. Belege, ohne daraus eine Bewertung abzuleiten.
</p> </p>
</header> </header>
<section class="panel" aria-labelledby="documented-consent-title"> <section class="panel" aria-labelledby="documented-consent-title">
<h2 id="documented-consent-title">Historische Consent-Zustände</h2> <h2 id="documented-consent-title">Lokal beobachtete Consent-Zustände</h2>
<p id="documented-consent-empty" class="empty-state" hidden> <p id="documented-consent-empty" class="empty-state" hidden>
Keine dokumentierten Consent-Zustände vorhanden. Keine dokumentierten Consent-Zustände vorhanden.
</p> </p>
@@ -47,6 +47,7 @@
<section class="consent-detail" aria-labelledby="consent-detail-title"> <section class="consent-detail" aria-labelledby="consent-detail-title">
<h2 id="consent-detail-title">Ausgewählter Consent-Zustand</h2> <h2 id="consent-detail-title">Ausgewählter Consent-Zustand</h2>
<div id="consent-detail-observation"></div> <div id="consent-detail-observation"></div>
<div id="consent-detail-diff"></div>
<div id="consent-detail-basics"></div> <div id="consent-detail-basics"></div>
<div id="consent-detail-summary"></div> <div id="consent-detail-summary"></div>
<div id="consent-detail-publisher"></div> <div id="consent-detail-publisher"></div>
Datei-Diff unterdrückt, da er zu groß ist Diff laden
+78
Datei anzeigen
@@ -1,9 +1,87 @@
console.log("VendorGet content listener loaded:", window.location.href); console.log("VendorGet content listener loaded:", window.location.href);
let latestObservedPreConsentCapture = null;
let latestObservedConsentCompleted = false;
browser.runtime.onMessage.addListener((message) => {
if (message?.type !== "probe_tcf_state") {
return undefined;
}
return probeTcfState();
});
function probeTcfState() {
if (latestObservedConsentCompleted) {
return Promise.resolve({
success: true,
capture: null,
source: "content_completed"
});
}
if (latestObservedPreConsentCapture && !latestObservedConsentCompleted) {
return Promise.resolve({
success: true,
capture: latestObservedPreConsentCapture,
source: "content_memory"
});
}
return new Promise((resolve) => {
const requestId = [
"tcf-probe",
Date.now().toString(36),
Math.random().toString(36).slice(2)
].join("-");
const timeoutId = setTimeout(() => {
window.removeEventListener("VendorGetTcfProbeResponse", handleResponse);
resolve({
success: false,
capture: null,
error: "tcf_probe_timeout"
});
}, 1000);
function handleResponse(event) {
if (event?.detail?.requestId !== requestId) {
return;
}
clearTimeout(timeoutId);
window.removeEventListener("VendorGetTcfProbeResponse", handleResponse);
resolve({
success: event.detail.success === true,
capture: event.detail.capture ?? null,
source: "tcf_get_tc_data"
});
}
window.addEventListener("VendorGetTcfProbeResponse", handleResponse);
window.dispatchEvent(new CustomEvent("VendorGetTcfProbeRequest", {
detail: {
requestId
}
}));
});
}
window.addEventListener("VendorGetFromPage", async (event) => { window.addEventListener("VendorGetFromPage", async (event) => {
console.log("VendorGet message from page:", event.detail); console.log("VendorGet message from page:", event.detail);
if (
event.detail?.eventName === "tcf_pre_consent_event" &&
!latestObservedConsentCompleted
) {
latestObservedPreConsentCapture = event.detail.payload ?? null;
}
if (event.detail?.eventName === "consent_capture") {
latestObservedConsentCompleted = true;
latestObservedPreConsentCapture = null;
}
await browser.runtime.sendMessage({ await browser.runtime.sendMessage({
type: "vendorget_capture", type: "vendorget_capture",
payload: event.detail payload: event.detail
+374
Datei anzeigen
@@ -0,0 +1,374 @@
"use strict";
var CONSENT_DIFF_RULE_ID = "consent_diff";
var CONSENT_DIFF_RULE_VERSION = 1;
const CONSENT_DIFF_SCALAR_AXES = [
"tcString",
"addtlConsent",
"vendorListVersion",
"cmpId",
"cmpVersion",
"tcfPolicyVersion",
"gdprApplies",
"cmpStatus"
];
const CONSENT_DIFF_OBJECT_AXES = [
"purpose.consents",
"vendor.consents",
"specialFeatureOptins"
];
function buildConsentDiff({
captureSessionId,
providerAnnouncement,
consentExecution,
derivedAt
}) {
const providerRaw = cloneConsentDiffValue(
providerAnnouncement?.rawTcData ?? null
);
const executionRaw = cloneConsentDiffValue(consentExecution?.rawTcData ?? null);
const providerWork = buildConsentDiffWorkCopy(
providerAnnouncement,
providerRaw
);
const executionWork = buildConsentDiffWorkCopy(consentExecution, executionRaw);
const axes = {};
CONSENT_DIFF_SCALAR_AXES.forEach((axis) => {
axes[axis] = compareConsentDiffScalarAxis(
axis,
providerWork,
executionWork
);
});
CONSENT_DIFF_OBJECT_AXES.forEach((axis) => {
axes[axis] = compareConsentDiffObjectAxis(
axis,
providerWork,
executionWork
);
});
return {
ruleId: CONSENT_DIFF_RULE_ID,
ruleVersion: CONSENT_DIFF_RULE_VERSION,
derivedAt,
captureSessionId,
sources: {
providerAnnouncement: buildConsentDiffSourceReference(
providerAnnouncement
),
consentExecution: buildConsentDiffSourceReference(consentExecution)
},
sourceSelection: {
providerAnnouncement:
"first_provider_announcement_for_capture_session_available_in_memory",
consentExecution: "current_useractioncomplete_execution_event"
},
comparisonAxes: [
...CONSENT_DIFF_SCALAR_AXES,
...CONSENT_DIFF_OBJECT_AXES
],
axes,
notes: buildConsentDiffNotes(providerAnnouncement, consentExecution)
};
}
function buildConsentDiffWorkCopy(eventRecord, rawTcData) {
return {
eventRecord: cloneConsentDiffValue(eventRecord ?? null),
rawTcData: rawTcData ?? null
};
}
function compareConsentDiffScalarAxis(axis, providerWork, executionWork) {
const providerValue = readConsentDiffAxisValue(providerWork, axis);
const executionValue = readConsentDiffAxisValue(executionWork, axis);
return {
kind: "scalar",
status: getConsentDiffScalarStatus(providerValue, executionValue),
provider: buildConsentDiffAxisSide(providerValue),
execution: buildConsentDiffAxisSide(executionValue)
};
}
function compareConsentDiffObjectAxis(axis, providerWork, executionWork) {
const providerValue = readConsentDiffAxisValue(providerWork, axis);
const executionValue = readConsentDiffAxisValue(executionWork, axis);
const providerObject = isConsentDiffPlainObject(providerValue.value)
? providerValue.value
: null;
const executionObject = isConsentDiffPlainObject(executionValue.value)
? executionValue.value
: null;
const providerKeys = providerObject ? Object.keys(providerObject).sort() : [];
const executionKeys = executionObject ? Object.keys(executionObject).sort() : [];
const allKeys = Array.from(new Set([...providerKeys, ...executionKeys])).sort();
const added = [];
const removed = [];
const changed = [];
const unchanged = [];
allKeys.forEach((key) => {
const inProvider = providerObject
? Object.prototype.hasOwnProperty.call(providerObject, key)
: false;
const inExecution = executionObject
? Object.prototype.hasOwnProperty.call(executionObject, key)
: false;
if (inProvider && !inExecution) {
removed.push({
key,
providerValue: cloneConsentDiffValue(providerObject[key])
});
return;
}
if (!inProvider && inExecution) {
added.push({
key,
executionValue: cloneConsentDiffValue(executionObject[key])
});
return;
}
if (
stableConsentDiffValue(providerObject[key]) ===
stableConsentDiffValue(executionObject[key])
) {
unchanged.push(key);
return;
}
changed.push({
key,
providerValue: cloneConsentDiffValue(providerObject[key]),
executionValue: cloneConsentDiffValue(executionObject[key])
});
});
return {
kind: "object",
status: getConsentDiffObjectStatus(
providerValue,
executionValue,
added,
removed,
changed
),
provider: {
present: providerValue.present,
sourcePath: providerValue.sourcePath,
keys: providerKeys
},
execution: {
present: executionValue.present,
sourcePath: executionValue.sourcePath,
keys: executionKeys
},
addedKeys: added,
removedKeys: removed,
changedKeys: changed,
unchangedKeys: unchanged,
counts: {
providerKeys: providerKeys.length,
executionKeys: executionKeys.length,
added: added.length,
removed: removed.length,
changed: changed.length,
unchanged: unchanged.length
}
};
}
function readConsentDiffAxisValue(workCopy, axis) {
const rawValue = readConsentDiffPath(workCopy.rawTcData, axis);
if (rawValue.present) {
return {
...rawValue,
sourcePath: `rawTcData.${axis}`
};
}
const eventValue = readConsentDiffPath(workCopy.eventRecord, axis);
if (eventValue.present) {
return {
...eventValue,
sourcePath: axis
};
}
return {
present: false,
value: undefined,
sourcePath: null
};
}
function readConsentDiffPath(value, path) {
if (value === null || value === undefined) {
return {
present: false,
value: undefined
};
}
const parts = path.split(".");
let current = value;
for (const part of parts) {
if (
current === null ||
current === undefined ||
!Object.prototype.hasOwnProperty.call(Object(current), part)
) {
return {
present: false,
value: undefined
};
}
current = current[part];
}
return {
present: true,
value: cloneConsentDiffValue(current)
};
}
function getConsentDiffScalarStatus(providerValue, executionValue) {
if (!providerValue.present && !executionValue.present) {
return "absent";
}
if (providerValue.present && !executionValue.present) {
return "removed";
}
if (!providerValue.present && executionValue.present) {
return "added";
}
return stableConsentDiffValue(providerValue.value) ===
stableConsentDiffValue(executionValue.value)
? "unchanged"
: "changed";
}
function getConsentDiffObjectStatus(
providerValue,
executionValue,
added,
removed,
changed
) {
if (!providerValue.present && !executionValue.present) {
return "absent";
}
if (providerValue.present && !executionValue.present) {
return "removed";
}
if (!providerValue.present && executionValue.present) {
return "added";
}
return added.length === 0 && removed.length === 0 && changed.length === 0
? "unchanged"
: "changed";
}
function buildConsentDiffAxisSide(axisValue) {
return {
present: axisValue.present,
sourcePath: axisValue.sourcePath,
value: axisValue.present ? cloneConsentDiffValue(axisValue.value) : null
};
}
function buildConsentDiffSourceReference(eventRecord) {
if (!eventRecord) {
return {
available: false,
reason: "missing_source_event"
};
}
return {
available: true,
store: "consent_events",
id: eventRecord.id ?? null,
eventType: eventRecord.eventType ?? null,
rawEventName: eventRecord.rawEventName ?? null,
capturedAt: eventRecord.capturedAt ?? null,
recordedAt: eventRecord.recordedAt ?? null,
captureSessionId: eventRecord.captureSessionId ?? null
};
}
function buildConsentDiffNotes(providerAnnouncement, consentExecution) {
const notes = [];
if (!providerAnnouncement) {
notes.push("provider_announcement_source_missing");
}
if (!consentExecution) {
notes.push("consent_execution_source_missing");
}
notes.push("primary_evidence_not_modified");
notes.push("no_legal_assessment");
notes.push("no_gvl_semantic_assessment");
return notes;
}
function stableConsentDiffValue(value) {
if (value === undefined) {
return "[[undefined]]";
}
if (value === null || typeof value !== "object") {
return JSON.stringify(value);
}
if (Array.isArray(value)) {
return `[${value.map((item) => stableConsentDiffValue(item)).join(",")}]`;
}
return `{${Object.keys(value)
.sort()
.map((key) => `${JSON.stringify(key)}:${stableConsentDiffValue(value[key])}`)
.join(",")}}`;
}
function cloneConsentDiffValue(value) {
if (value === undefined) {
return undefined;
}
if (value === null || typeof value !== "object") {
return value;
}
return JSON.parse(JSON.stringify(value));
}
function isConsentDiffPlainObject(value) {
return (
value !== null &&
typeof value === "object" &&
!Array.isArray(value)
);
}
+67 -7
Datei anzeigen
@@ -287,14 +287,21 @@ async function importVendorGetGvlRevisionEvidenceJson(exportContainer) {
}; };
} }
async function markVendorGetGvlRevisionEvidenceVaultCopy(snapshotSha256) { async function markVendorGetGvlRevisionEvidenceVaultCopy(
snapshotSha256,
verification = null
) {
if (!snapshotSha256) { if (!snapshotSha256) {
throw new Error("missing_snapshot_sha256"); throw new Error("missing_snapshot_sha256");
} }
const db = await openVendorGetDb(); const db = await openVendorGetDb();
return markGvlRevisionEvidenceVaultCopyAvailable(db, snapshotSha256); return markGvlRevisionEvidenceVaultCopyAvailable(
db,
snapshotSha256,
verification
);
} }
function getGvlEvidenceRecordByKey(db, storeName, key) { function getGvlEvidenceRecordByKey(db, storeName, key) {
@@ -632,9 +639,16 @@ function formatGvlEvidenceProvenance(values) {
return "web"; return "web";
} }
function markGvlRevisionEvidenceVaultCopyAvailable(db, snapshotSha256) { function markGvlRevisionEvidenceVaultCopyAvailable(
return updateGvlRevisionEvidenceRecords(db, snapshotSha256, (record) => db,
markGvlEvidenceRecordVaultCopyAvailable(record) 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) => { return new Promise((resolve, reject) => {
const tx = db.transaction( const tx = db.transaction(
[VENDORGET_STORE_NAMES.gvlSnapshots, VENDORGET_STORE_NAMES.gvlRawEvidence], [VENDORGET_STORE_NAMES.gvlSnapshots, VENDORGET_STORE_NAMES.gvlRawEvidence],
@@ -665,7 +684,8 @@ function updateGvlRevisionEvidenceRecords(db, snapshotSha256, updateRecord) {
snapshotMarked: false, snapshotMarked: false,
rawEvidenceMarked: false, rawEvidenceMarked: false,
snapshotSha256, snapshotSha256,
rawGvlSha256: null rawGvlSha256: null,
skippedReason: null
}; };
snapshotRequest.onerror = () => reject(snapshotRequest.error); snapshotRequest.onerror = () => reject(snapshotRequest.error);
@@ -673,6 +693,12 @@ function updateGvlRevisionEvidenceRecords(db, snapshotSha256, updateRecord) {
const snapshot = snapshotRequest.result ?? null; const snapshot = snapshotRequest.result ?? null;
if (!snapshot) { if (!snapshot) {
result.skippedReason = "gvl_snapshot_not_found";
return;
}
if (!doesGvlRevisionEvidenceMatchVerification(snapshot, verification)) {
result.skippedReason = "gvl_revision_evidence_verification_mismatch";
return; 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) { function formatGvlEvidenceUtcCompact(date) {
return [ return [
date.getUTCFullYear(), date.getUTCFullYear(),
+24 -8
Datei anzeigen
@@ -127,14 +127,14 @@ th:last-child {
text-align: right; text-align: right;
} }
.explorer-actions { .workspace-actions {
display: flex; display: grid;
flex-wrap: wrap; grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px; gap: 10px;
margin-top: 14px; margin-top: 14px;
} }
.button-link, .workspace-link,
button { button {
padding: 8px 10px; padding: 8px 10px;
border: 1px solid #475569; border: 1px solid #475569;
@@ -145,12 +145,27 @@ button {
background: #1f2937; background: #1f2937;
} }
.button-link { .workspace-link {
display: inline-flex; display: grid;
align-items: center; gap: 5px;
text-decoration: none; text-decoration: none;
} }
.workspace-link strong {
font-size: 13px;
}
.workspace-link span {
font-size: 12px;
line-height: 1.35;
color: #cbd5e1;
}
.workspace-placeholder {
color: #cbd5e1;
background: #172033;
}
button:disabled { button:disabled {
cursor: default; cursor: default;
opacity: 0.65; opacity: 0.65;
@@ -165,7 +180,8 @@ button:disabled {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.explorer-actions { .workspace-actions {
display: grid; display: grid;
grid-template-columns: 1fr;
} }
} }
+49 -31
Datei anzeigen
@@ -1,26 +1,30 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="de">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>VendorGet-IV Evidence Dashboard</title> <title>VG-Observe Dashboard</title>
<link rel="stylesheet" href="dashboard.css"> <link rel="stylesheet" href="dashboard.css">
</head> </head>
<body> <body>
<main class="dashboard"> <main class="dashboard">
<header class="dashboard-header"> <header class="dashboard-header">
<h1>VendorGet-IV Evidence Dashboard</h1> <h1>VG-Observe Dashboard</h1>
<div id="dashboard-status" class="dashboard-status" aria-live="polite"> <div id="dashboard-status" class="dashboard-status" aria-live="polite">
Loading evidence status Lade lokalen Beobachtungsstatus
</div> </div>
<p class="dashboard-notice"> <p class="dashboard-notice">
Übersicht und Einstieg für VG-Observe. Detailansichten liegen in Was wurde lokal beobachtet? Diese Übersicht zeigt den Workspace-
eigenen Explorern. Bestand und führt zu den fachlichen Arbeitsansichten.
</p> </p>
</header> </header>
<section class="panel" aria-labelledby="stores-title"> <section class="panel" aria-labelledby="stores-title">
<h2 id="stores-title">Evidence Stores</h2> <h2 id="stores-title">Lokaler Evidence-Bestand</h2>
<p class="section-help">
Bestandsübersicht der lokal gespeicherten Beobachtungen und
Referenzdaten. Die Zahlen sind Inventar, keine Bewertung.
</p>
<table> <table>
<thead> <thead>
<tr> <tr>
@@ -53,13 +57,38 @@
</table> </table>
</section> </section>
<section class="panel" aria-labelledby="official-gvl-title"> <section class="panel" aria-labelledby="workspaces-title">
<h2 id="official-gvl-title">Offizielle Vendorliste</h2> <h2 id="workspaces-title">Arbeitsbereiche</h2>
<p class="section-help"> <p class="section-help">
Die aktuell offiziell abgerufene IAB-Europe-Vendorliste ist die Oben verstehen, in der Mitte arbeiten, unten beweisen: Die
Version, die VG-Observe direkt von der offiziellen IAB-Europe-Quelle Detailansichten trennen Untersuchung, technische Beobachtung,
geladen hat. Sie ist getrennt von der Vendorliste, die in einem Referenz/Vault und Analyse-Vorbereitung.
konkreten Consent-Kontext gemeldet wurde. </p>
<div class="workspace-actions">
<a class="workspace-link" href="../consent-explorer/consent-explorer.html">
<strong>Consent untersuchen</strong>
<span>Zentrale Ansicht für dokumentierte Consent-Zustände.</span>
</a>
<a class="workspace-link" href="../request-explorer/request-explorer.html">
<strong>Requests prüfen</strong>
<span>Technisch beobachtete Browser-Requests ohne Bewertung.</span>
</a>
<a class="workspace-link" href="../gvl-explorer/gvl-explorer.html">
<strong>GVL-Referenz/Vault</strong>
<span>Vendorlisten, Revision-Evidence und Vault-Transport.</span>
</a>
<a class="workspace-link" href="../analysis-dashboard/analysis-dashboard.html">
<strong>Analyse-Vorbereitung</strong>
<span>Datenbestände und vorbereitete Prüffelder, keine Engine.</span>
</a>
</div>
</section>
<section class="panel" aria-labelledby="official-gvl-title">
<h2 id="official-gvl-title">GVL-Referenzstatus</h2>
<p class="section-help">
Die GVL ist Referenzbasis für spätere Rekonstruktion. Dieser Status
zeigt nur den lokalen Referenzbestand und letzte Update-Hinweise.
</p> </p>
<dl class="gvl-status-grid"> <dl class="gvl-status-grid">
<div> <div>
@@ -89,27 +118,16 @@
</dl> </dl>
</section> </section>
<section class="panel" aria-labelledby="explorers-title"> <section class="panel" aria-labelledby="data-maintenance-title">
<h2 id="explorers-title">Explorer</h2> <h2 id="data-maintenance-title">Datenpflege</h2>
<p class="section-help"> <div class="workspace-actions">
Historische Consent-Zustände und technische Belege werden in einer <a class="workspace-link workspace-placeholder" href="../data-maintenance/data-maintenance.html">
eigenen Ansicht geöffnet. <strong>Datenpflege</strong>
</p> <span>Verwaltung lokaler Datenbestände.</span>
<div class="explorer-actions">
<a class="button-link" href="../consent-explorer/consent-explorer.html">
Consent-Explorer öffnen
</a>
<a class="button-link" href="../gvl-explorer/gvl-explorer.html">
GVL-Explorer öffnen
</a>
<a class="button-link" href="../request-explorer/request-explorer.html">
Request-Explorer öffnen
</a>
<a class="button-link" href="../analysis-dashboard/analysis-dashboard.html">
Request-/Empfänger-Analyse öffnen
</a> </a>
</div> </div>
</section> </section>
</main> </main>
<script src="dashboard.js"></script> <script src="dashboard.js"></script>
+2 -2
Datei anzeigen
@@ -40,9 +40,9 @@ async function renderEvidenceStatus() {
} }
renderStoreCounts(status.storeCounts ?? {}); renderStoreCounts(status.storeCounts ?? {});
renderStatusMessage("Evidence status loaded"); renderStatusMessage("Lokaler Beobachtungsstatus geladen");
} catch (error) { } catch (error) {
renderStatusMessage("Evidence status could not be loaded"); renderStatusMessage("Lokaler Beobachtungsstatus konnte nicht geladen werden");
console.warn("VendorGet-IV dashboard status failed", error); console.warn("VendorGet-IV dashboard status failed", error);
} }
} }
+160
Datei anzeigen
@@ -0,0 +1,160 @@
* {
box-sizing: border-box;
}
body {
margin: 0;
min-width: 320px;
font-family: Arial, sans-serif;
color: #e5edf5;
background: #111827;
}
.maintenance {
width: min(1040px, 100%);
margin: 0 auto;
padding: 24px;
}
.maintenance-header {
display: grid;
gap: 8px;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid #334155;
}
h1,
h2,
h3,
h4,
p {
margin: 0;
}
h1 {
font-size: 24px;
font-weight: 700;
}
h2 {
margin-bottom: 12px;
font-size: 15px;
font-weight: 700;
}
h3 {
font-size: 13px;
color: #e5edf5;
}
h4 {
font-size: 13px;
color: #e5edf5;
}
p {
max-width: 760px;
font-size: 13px;
line-height: 1.5;
color: #cbd5e1;
}
.back-link {
width: fit-content;
color: #bfdbfe;
font-size: 13px;
}
.panel {
margin-bottom: 22px;
padding-bottom: 20px;
border-bottom: 1px solid #334155;
}
.section-help {
margin-bottom: 12px;
}
.segment-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.segment-card,
.danger-action {
display: grid;
gap: 8px;
min-width: 0;
padding: 12px;
border: 1px solid #334155;
border-radius: 4px;
background: #1f2937;
}
.segment-action {
display: grid;
gap: 8px;
padding: 10px;
border: 1px solid #475569;
border-radius: 4px;
background: #172033;
}
.segment-status {
width: fit-content;
max-width: 100%;
padding: 5px 8px;
border: 1px solid #475569;
border-radius: 4px;
font-weight: 700;
color: #cbd5e1;
background: #172033;
}
.protected-revisions {
white-space: pre-line;
}
button {
width: fit-content;
max-width: 100%;
padding: 8px 10px;
border: 1px solid #475569;
border-radius: 4px;
font: inherit;
font-size: 13px;
color: #e5edf5;
background: #1f2937;
}
button:disabled {
cursor: default;
opacity: 0.65;
}
.danger-panel {
border-bottom: 0;
}
.danger-action {
border-color: #7f1d1d;
background: #1f1518;
}
.danger-action .segment-status {
border-color: #7f1d1d;
color: #fecaca;
background: #450a0a;
}
@media (max-width: 640px) {
.maintenance {
padding: 16px;
}
.segment-grid {
grid-template-columns: 1fr;
}
}
@@ -0,0 +1,104 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>VG-Observe Datenpflege</title>
<link rel="stylesheet" href="data-maintenance.css">
</head>
<body>
<main class="maintenance">
<header class="maintenance-header">
<a class="back-link" href="../dashboard/dashboard.html">Zurück zum Observe-Dashboard</a>
<h1>Datenpflege</h1>
<p class="section-help">
Lokale Datenbestände verwalten und bereinigen.
</p>
</header>
<section class="panel" aria-labelledby="segments-title">
<h2 id="segments-title">Datenbereiche</h2>
<div class="segment-grid">
<article class="segment-card">
<h3>GVL-Referenzdaten</h3>
<p>
Vendorlisten, Revisionen und GVL-Referenzbestände.
</p>
<div class="segment-action">
<h4>GVL-Referenzdaten bereinigen</h4>
<p>
Entfernt lokale GVL-Referenzdaten aus der Browser-Datenbank.
Consent-Daten, Request-Beobachtungen und Analyse-Daten bleiben
unberührt.
</p>
<button id="purge-gvl-reference-data-button" type="button" disabled>
GVL-Referenzdaten bereinigen
</button>
</div>
<p
id="gvl-reference-maintenance-status"
class="segment-status"
aria-live="polite"
>
</p>
<p
id="gvl-reference-protected-revisions"
class="protected-revisions"
aria-live="polite"
hidden
></p>
<p id="gvl-reference-maintenance-message">
Keine GVL-Revisionen.
</p>
</article>
<article class="segment-card">
<h3>Consent-Daten</h3>
<p>
Consent-Zustände und Ereignisse.
</p>
<p class="segment-status">
Noch nicht verfügbar.
</p>
</article>
<article class="segment-card">
<h3>Analyse-Daten</h3>
<p>
Analyse- und Arbeitsdaten.
</p>
<p class="segment-status">
Noch nicht verfügbar.
</p>
</article>
<article class="segment-card">
<h3>Weitere Datenbereiche</h3>
<p>
Reserviert für zukünftige Erweiterungen.
</p>
<p class="segment-status">
Noch nicht verfügbar.
</p>
</article>
</div>
</section>
<section class="panel danger-panel" aria-labelledby="danger-title">
<h2 id="danger-title">Gefahrenbereich</h2>
<p class="section-help">
Irreversible Aktionen für den gesamten lokalen Datenbestand.
</p>
<article class="danger-action" aria-label="Vorbereitete Gefahrenaktion">
<h3>Gesamten lokalen Datenbestand löschen</h3>
<p class="segment-status">Noch nicht verfügbar.</p>
<p>
Erfordert später eine ausdrückliche Sicherheitsbestätigung.
</p>
</article>
</section>
</main>
<script src="data-maintenance.js"></script>
</body>
</html>
+119
Datei anzeigen
@@ -0,0 +1,119 @@
"use strict";
const gvlReferenceMaintenanceStatus = document.getElementById(
"gvl-reference-maintenance-status"
);
const gvlReferenceProtectedRevisions = document.getElementById(
"gvl-reference-protected-revisions"
);
const gvlReferenceMaintenanceMessage = document.getElementById(
"gvl-reference-maintenance-message"
);
const purgeGvlReferenceDataButton = document.getElementById(
"purge-gvl-reference-data-button"
);
document.addEventListener("DOMContentLoaded", async () => {
purgeGvlReferenceDataButton.addEventListener("click", async () => {
await purgeUnlockedEvidenceRecords();
});
await renderGvlReferenceMaintenanceStatus();
});
async function renderGvlReferenceMaintenanceStatus() {
try {
const result = await browser.runtime.sendMessage({
type: "list_gvl_snapshots"
});
if (!result?.success) {
throw new Error(result?.error ?? "list_gvl_snapshots_failed");
}
renderGvlReferenceSnapshotStatus(result.gvlSnapshots ?? []);
} catch (error) {
renderGvlReferenceStatus("–", true, "Status nicht verfügbar.");
renderProtectedRevisions([]);
console.warn("VG-Observe GVL maintenance status failed", error);
}
}
function renderGvlReferenceSnapshotStatus(snapshots) {
if (!snapshots.length) {
renderGvlReferenceStatus("–", true, "Keine GVL-Revisionen.");
renderProtectedRevisions([]);
return;
}
const protectedRevisions = snapshots
.filter((snapshot) => snapshot.workspaceDeleteProtected === true)
.map((snapshot) => snapshot.vendorListVersion)
.filter((vendorListVersion) => vendorListVersion !== null)
.filter((vendorListVersion) => vendorListVersion !== undefined);
const allRevisionsDeleteAllowed = snapshots.every((snapshot) => {
return snapshot.workspaceDeleteAllowed === true;
});
if (allRevisionsDeleteAllowed) {
renderGvlReferenceStatus("🔓", false, "GVL-Revisionen löschbar.");
} else if (protectedRevisions.length) {
renderGvlReferenceStatus("🔒", true, "GVL-Revisionen geschützt.");
} else {
renderGvlReferenceStatus("–", true, "Status nicht verfügbar.");
}
renderProtectedRevisions(protectedRevisions);
}
function renderGvlReferenceStatus(statusSymbol, buttonDisabled, message) {
gvlReferenceMaintenanceStatus.textContent = statusSymbol;
purgeGvlReferenceDataButton.disabled = buttonDisabled;
gvlReferenceMaintenanceMessage.textContent = message;
}
async function purgeUnlockedEvidenceRecords() {
if (
!confirm(
"Ungesperrte Evidence-Daten mit bestehender Schutzlogik bereinigen?"
)
) {
return;
}
purgeGvlReferenceDataButton.disabled = true;
gvlReferenceMaintenanceMessage.textContent = "Bereinigung läuft...";
try {
const result = await browser.runtime.sendMessage({
type: "purge_unlocked_evidence_records"
});
if (!result?.success) {
throw new Error(result?.error ?? "purge_unlocked_evidence_records_failed");
}
await renderGvlReferenceMaintenanceStatus();
gvlReferenceMaintenanceMessage.textContent = buildPurgeSuccessMessage(result);
} catch (error) {
await renderGvlReferenceMaintenanceStatus();
gvlReferenceMaintenanceMessage.textContent = "Bereinigung fehlgeschlagen.";
console.warn("VG-Observe protected purge failed", error);
}
}
function buildPurgeSuccessMessage(result) {
if (Number.isFinite(result.deletedCount)) {
return `Bereinigung abgeschlossen: ${result.deletedCount} Records.`;
}
return "Bereinigung abgeschlossen.";
}
function renderProtectedRevisions(vendorListVersions) {
gvlReferenceProtectedRevisions.hidden = vendorListVersions.length === 0;
gvlReferenceProtectedRevisions.textContent = vendorListVersions.length
? `Geschützt: ${vendorListVersions
.map((vendorListVersion) => String(vendorListVersion))
.join(", ")}`
: "";
}
+17 -17
Datei anzeigen
@@ -3,32 +3,32 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>VG-Observe GVL-Explorer</title> <title>VG-Observe GVL-Referenz/Vault</title>
<link rel="stylesheet" href="gvl-explorer.css"> <link rel="stylesheet" href="gvl-explorer.css">
</head> </head>
<body> <body>
<main class="explorer"> <main class="explorer">
<header class="explorer-header"> <header class="explorer-header">
<a class="back-link" href="../dashboard/dashboard.html">Zurück zum Dashboard</a> <a class="back-link" href="../dashboard/dashboard.html">Zurück zum Observe-Dashboard</a>
<h1>GVL-Explorer</h1> <h1>GVL-Referenz/Vault</h1>
<p class="section-help"> <p class="section-help">
Diese Ansicht zeigt lokal gespeicherte offizielle Die GVL ist Referenzbasis und Evidence-Quelle für reproduzierbare
IAB-Europe-Vendorlisten. Sie dient dazu, historische Rekonstruktion. Sie ist nicht das untersuchte Consent-Ereignis
Vendorlisten-Versionen nachvollziehbar zu machen. selbst; Import, Export und Verifikation sichern den Referenzbestand.
</p> </p>
</header> </header>
<section class="panel" aria-labelledby="snapshot-list-title"> <section class="panel" aria-labelledby="snapshot-list-title">
<h2 id="snapshot-list-title">Gespeicherte Vendorlisten</h2> <h2 id="snapshot-list-title">GVL-Revisionen im Workspace</h2>
<div class="fetch-actions"> <div class="fetch-actions">
<button id="gvl-fetch-official-button" type="button"> <button id="gvl-fetch-official-button" type="button">
GVL aus Web laden GVL-Referenz aus Web laden
</button> </button>
<button id="gvl-revision-evidence-export-button" type="button"> <button id="gvl-revision-evidence-export-button" type="button">
Ausgewählte GVL-Revision exportieren Ausgewählte Revision in den Vault exportieren
</button> </button>
<label class="file-action" for="gvl-revision-evidence-verify-input"> <label class="file-action" for="gvl-revision-evidence-verify-input">
GVL-Revision-Export verifizieren Vault-Paket verifizieren
</label> </label>
<input <input
id="gvl-revision-evidence-verify-input" id="gvl-revision-evidence-verify-input"
@@ -37,7 +37,7 @@
accept="application/json,.json" accept="application/json,.json"
> >
<label class="file-action" for="gvl-revision-evidence-import-input"> <label class="file-action" for="gvl-revision-evidence-import-input">
GVL-Revision-Evidence importieren Revision aus Vault importieren
</label> </label>
<input <input
id="gvl-revision-evidence-import-input" id="gvl-revision-evidence-import-input"
@@ -55,7 +55,7 @@
aria-live="polite" aria-live="polite"
></div> ></div>
<p id="gvl-snapshot-empty" class="empty-state" hidden> <p id="gvl-snapshot-empty" class="empty-state" hidden>
Keine gespeicherten offiziellen Vendorlisten vorhanden. Keine gespeicherten GVL-Referenzrevisionen vorhanden.
</p> </p>
<div id="gvl-snapshot-content" hidden> <div id="gvl-snapshot-content" hidden>
<div class="snapshot-list-wrap"> <div class="snapshot-list-wrap">
@@ -76,10 +76,10 @@
</div> </div>
<section class="snapshot-summary" aria-labelledby="snapshot-summary-title"> <section class="snapshot-summary" aria-labelledby="snapshot-summary-title">
<h2 id="snapshot-summary-title">Ausgewählte Vendorliste</h2> <h2 id="snapshot-summary-title">Ausgewählte GVL-Revision</h2>
<div class="rebuild-actions"> <div class="rebuild-actions">
<button id="gvl-rebuild-normalized-button" type="button" disabled> <button id="gvl-rebuild-normalized-button" type="button" disabled>
Lokale GVL-Daten neu aufbauen (Reparatur) Lokale Referenzdaten neu aufbauen (Reparatur)
</button> </button>
<span <span
id="gvl-rebuild-normalized-status" id="gvl-rebuild-normalized-status"
@@ -92,7 +92,7 @@
<details id="gvl-vendor-overview-details" class="vendor-overview"> <details id="gvl-vendor-overview-details" class="vendor-overview">
<summary id="gvl-vendor-overview-summary"> <summary id="gvl-vendor-overview-summary">
Vendoren-Übersicht anzeigen Vendoren-Referenz anzeigen
</summary> </summary>
<p id="gvl-vendor-overview-empty" class="empty-state" hidden> <p id="gvl-vendor-overview-empty" class="empty-state" hidden>
Keine normalisierten Vendoren für diese Vendorliste vorhanden. Keine normalisierten Vendoren für diese Vendorliste vorhanden.
@@ -123,7 +123,7 @@
</section> </section>
<section class="panel" aria-labelledby="vendor-detail-title"> <section class="panel" aria-labelledby="vendor-detail-title">
<h2 id="vendor-detail-title">Lokaler Vendor-Nachweis</h2> <h2 id="vendor-detail-title">Lokaler Vendor-Referenznachweis</h2>
<form id="gvl-vendor-detail-form" class="vendor-detail-form"> <form id="gvl-vendor-detail-form" class="vendor-detail-form">
<label for="gvl-vendor-id-input">Vendor-ID</label> <label for="gvl-vendor-id-input">Vendor-ID</label>
<input <input
@@ -135,7 +135,7 @@
placeholder="977" placeholder="977"
> >
<button id="gvl-vendor-detail-button" type="submit"> <button id="gvl-vendor-detail-button" type="submit">
Vendor anzeigen Vendor-Referenz anzeigen
</button> </button>
</form> </form>
<div <div
+30 -21
Datei anzeigen
@@ -112,7 +112,7 @@ async function fetchOfficialGvl() {
await renderGvlSnapshots(); await renderGvlSnapshots();
await renderSelectedGvlSnapshotSummary(); await renderSelectedGvlSnapshotSummary();
} catch (error) { } catch (error) {
renderFetchStatus("GVL aus Web konnte nicht geladen werden."); renderFetchStatus("GVL-Referenz aus Web konnte nicht geladen werden.");
console.warn("VG-Observe manual official GVL fetch failed", error); console.warn("VG-Observe manual official GVL fetch failed", error);
} finally { } finally {
gvlFetchOfficialButton.disabled = false; gvlFetchOfficialButton.disabled = false;
@@ -134,20 +134,20 @@ function buildGvlEvidenceConflictMessage(result) {
function buildGvlSyncStatusMessage(result) { function buildGvlSyncStatusMessage(result) {
if (result?.syncStatus === "new_gvl_revision_stored_and_normalized") { if (result?.syncStatus === "new_gvl_revision_stored_and_normalized") {
return "GVL aus Web geladen, neue Revision gespeichert und normalisiert."; return "GVL-Referenz aus Web geladen, neue Revision gespeichert und normalisiert.";
} }
if (result?.syncStatus === "known_gvl_rebuilt_from_local_evidence") { if (result?.syncStatus === "known_gvl_rebuilt_from_local_evidence") {
return "GVL aus Web geprüft; bekannte Revision aus lokaler Evidence neu aufgebaut."; return "GVL-Referenz aus Web geprüft; bekannte Revision aus lokaler Evidence neu aufgebaut.";
} }
if (result?.syncStatus === "current_and_locally_available") { if (result?.syncStatus === "current_and_locally_available") {
return "GVL aus Web geprüft; aktuelle Revision ist lokal vollständig verfügbar."; return "GVL-Referenz aus Web geprüft; aktuelle Revision ist lokal vollständig verfügbar.";
} }
return result?.alreadyKnown return result?.alreadyKnown
? "GVL aus Web geprüft; Revision ist lokal verfügbar." ? "GVL-Referenz aus Web geprüft; Revision ist lokal verfügbar."
: "GVL aus Web geladen."; : "GVL-Referenz aus Web geladen.";
} }
function renderFetchStatus(message) { function renderFetchStatus(message) {
@@ -215,7 +215,7 @@ async function exportSelectedGvlRevisionEvidenceJsonFile() {
} }
downloadGvlRevisionEvidenceJsonExport(result.export); downloadGvlRevisionEvidenceJsonExport(result.export);
await markGvlRevisionEvidenceVaultCopy(result.export); await markGvlRevisionEvidenceVaultCopy(result.export, verification);
await renderGvlSnapshots(); await renderGvlSnapshots();
renderGvlEvidenceTransportStatus( renderGvlEvidenceTransportStatus(
[ [
@@ -271,7 +271,10 @@ function downloadGvlRevisionEvidenceJsonExport(exportContainer) {
setTimeout(() => URL.revokeObjectURL(url), 0); setTimeout(() => URL.revokeObjectURL(url), 0);
} }
async function markGvlRevisionEvidenceVaultCopy(exportContainer) { async function markGvlRevisionEvidenceVaultCopy(
exportContainer,
verification = null
) {
const snapshotSha256 = exportContainer?.metadata?.snapshotSha256 ?? null; const snapshotSha256 = exportContainer?.metadata?.snapshotSha256 ?? null;
if (!snapshotSha256) { if (!snapshotSha256) {
@@ -281,7 +284,8 @@ async function markGvlRevisionEvidenceVaultCopy(exportContainer) {
const result = await browser.runtime.sendMessage({ const result = await browser.runtime.sendMessage({
type: "mark_gvl_revision_evidence_vault_copy", type: "mark_gvl_revision_evidence_vault_copy",
payload: { payload: {
snapshotSha256 snapshotSha256,
verification
} }
}); });
@@ -338,6 +342,11 @@ async function verifyGvlRevisionEvidenceJsonFile() {
exportContainer exportContainer
); );
if (verification.valid) {
await markGvlRevisionEvidenceVaultCopy(exportContainer, verification);
await renderGvlSnapshots();
}
renderGvlEvidenceTransportStatus( renderGvlEvidenceTransportStatus(
buildGvlRevisionEvidenceVerificationMessage(verification), buildGvlRevisionEvidenceVerificationMessage(verification),
verification.valid ? "success" : "error" verification.valid ? "success" : "error"
@@ -505,7 +514,7 @@ async function rebuildSelectedGvlSnapshotNormalizedData() {
} }
gvlRebuildNormalizedButton.disabled = true; gvlRebuildNormalizedButton.disabled = true;
renderRebuildStatus("Lokale Evidence wird neu normalisiert..."); renderRebuildStatus("Lokale Referenzdaten werden neu normalisiert...");
try { try {
const result = await browser.runtime.sendMessage({ const result = await browser.runtime.sendMessage({
@@ -528,7 +537,7 @@ async function rebuildSelectedGvlSnapshotNormalizedData() {
await renderGvlVendorDetail(); await renderGvlVendorDetail();
} }
} catch (error) { } catch (error) {
renderRebuildStatus("Lokaler Neuaufbau fehlgeschlagen."); renderRebuildStatus("Lokaler Referenz-Neuaufbau fehlgeschlagen.");
console.warn("VG-Observe GVL normalized rebuild failed", error); console.warn("VG-Observe GVL normalized rebuild failed", error);
} finally { } finally {
gvlRebuildNormalizedButton.disabled = gvlRebuildNormalizedButton.disabled =
@@ -540,7 +549,7 @@ function buildRebuildSuccessMessage(result) {
const counts = result.counts ?? {}; const counts = result.counts ?? {};
return [ return [
"Lokale GVL-Daten neu aufgebaut.", "Lokale Referenzdaten neu aufgebaut.",
`Vendoren: ${formatCount(counts.vendorCount)}`, `Vendoren: ${formatCount(counts.vendorCount)}`,
`Beziehungen: ${formatCount(counts.vendorRelationshipCount)}` `Beziehungen: ${formatCount(counts.vendorRelationshipCount)}`
].join(" "); ].join(" ");
@@ -1025,7 +1034,7 @@ async function renderGvlSnapshots() {
gvlSnapshotEmpty.hidden = false; gvlSnapshotEmpty.hidden = false;
gvlSnapshotContent.hidden = true; gvlSnapshotContent.hidden = true;
gvlSnapshotEmpty.textContent = gvlSnapshotEmpty.textContent =
"Gespeicherte Vendorlisten konnten nicht geladen werden."; "Gespeicherte GVL-Referenzen konnten nicht geladen werden.";
console.warn("VG-Observe GVL snapshot list failed", error); console.warn("VG-Observe GVL snapshot list failed", error);
} }
} }
@@ -1037,7 +1046,7 @@ function renderNoGvlSnapshots() {
gvlSnapshotEmpty.hidden = false; gvlSnapshotEmpty.hidden = false;
gvlSnapshotContent.hidden = true; gvlSnapshotContent.hidden = true;
gvlSnapshotEmpty.textContent = gvlSnapshotEmpty.textContent =
"Keine gespeicherten offiziellen Vendorlisten vorhanden."; "Keine gespeicherten GVL-Referenzrevisionen vorhanden.";
} }
function renderGvlSnapshotList() { function renderGvlSnapshotList() {
@@ -1124,7 +1133,7 @@ async function renderSelectedGvlSnapshotSummary() {
selectedSnapshotSummary = null; selectedSnapshotSummary = null;
updateRebuildActionState(null); updateRebuildActionState(null);
gvlSnapshotSummary.textContent = gvlSnapshotSummary.textContent =
"Zusammenfassung dieser Vendorliste konnte nicht geladen werden."; "Zusammenfassung dieser GVL-Revision konnte nicht geladen werden.";
console.warn("VG-Observe GVL snapshot summary failed", error); console.warn("VG-Observe GVL snapshot summary failed", error);
} }
} }
@@ -1156,7 +1165,7 @@ async function renderVendorOverviewForSelectedSnapshot() {
} catch (error) { } catch (error) {
gvlVendorOverviewEmpty.hidden = false; gvlVendorOverviewEmpty.hidden = false;
gvlVendorOverviewEmpty.textContent = gvlVendorOverviewEmpty.textContent =
"Vendoren-Übersicht konnte nicht geladen werden."; "Vendoren-Referenz konnte nicht geladen werden.";
console.warn("VG-Observe GVL vendor overview failed", error); console.warn("VG-Observe GVL vendor overview failed", error);
} }
} }
@@ -1168,7 +1177,7 @@ function renderVendorOverview() {
gvlVendorOverviewEmpty.hidden = false; gvlVendorOverviewEmpty.hidden = false;
gvlVendorOverviewContent.hidden = true; gvlVendorOverviewContent.hidden = true;
gvlVendorOverviewEmpty.textContent = gvlVendorOverviewEmpty.textContent =
"Keine normalisierten Vendoren für diese Vendorliste vorhanden."; "Keine normalisierten Vendor-Referenzen für diese GVL-Revision vorhanden.";
return; return;
} }
@@ -1219,8 +1228,8 @@ function updateVendorOverviewSummary() {
const count = selectedSnapshotVendors.length; const count = selectedSnapshotVendors.length;
gvlVendorOverviewSummary.textContent = count gvlVendorOverviewSummary.textContent = count
? `Vendoren-Übersicht anzeigen (${count})` ? `Vendoren-Referenz anzeigen (${count})`
: "Vendoren-Übersicht anzeigen"; : "Vendoren-Referenz anzeigen";
} }
function updateRebuildActionState(summary) { function updateRebuildActionState(summary) {
@@ -1234,11 +1243,11 @@ function updateRebuildActionState(summary) {
} }
if (needsRebuild) { if (needsRebuild) {
renderRebuildStatus("Reparatur möglich: normalisierte lokale Daten fehlen."); renderRebuildStatus("Reparatur möglich: normalisierte Referenzdaten fehlen.");
return; return;
} }
renderRebuildStatus("Normalisierte lokale GVL-Daten sind verfügbar."); renderRebuildStatus("Normalisierte lokale Referenzdaten sind verfügbar.");
} }
function doesSnapshotNeedNormalizedRebuild(summary) { function doesSnapshotNeedNormalizedRebuild(summary) {
+70 -41
Datei anzeigen
@@ -20,7 +20,7 @@
function cloneSerializable(value, seen) { function cloneSerializable(value, seen) {
if (value === undefined) { if (value === undefined) {
return null; return undefined;
} }
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") { if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
@@ -50,10 +50,6 @@
}); });
} }
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
return undefined;
}
const result = {}; const result = {};
Object.keys(value).forEach(function (key) { Object.keys(value).forEach(function (key) {
@@ -75,6 +71,64 @@
return result; return result;
} }
function buildTcfEventCapture(tcData) {
return {
url: window.location.href,
origin: window.location.origin,
timestampUtc: new Date().toISOString(),
cmpId: tcData.cmpId,
cmpVersion: tcData.cmpVersion,
gdprApplies: tcData.gdprApplies,
tcfPolicyVersion: tcData.tcfPolicyVersion,
vendorListVersion: tcData.vendorListVersion,
tcString: tcData.tcString,
eventStatus: tcData.eventStatus,
cmpStatus: tcData.cmpStatus,
isServiceSpecific: tcData.isServiceSpecific,
useNonStandardTexts: tcData.useNonStandardTexts,
publisherCC: tcData.publisherCC,
purposeOneTreatment: tcData.purposeOneTreatment,
purpose: tcData.purpose,
vendor: tcData.vendor,
specialFeatureOptins: tcData.specialFeatureOptins,
publisher: tcData.publisher,
addtlConsent: tcData.addtlConsent,
rawTcString: {
tcString: tcData.tcString,
addtlConsent: tcData.addtlConsent
},
rawTcData: cloneSerializable(tcData)
};
}
window.addEventListener("VendorGetTcfProbeRequest", function (event) {
const requestId = event?.detail?.requestId ?? null;
window.__tcfapi("getTCData", 2, function (tcData, success) {
window.dispatchEvent(new CustomEvent("VendorGetTcfProbeResponse", {
detail: {
requestId: requestId,
success: success,
capture: success && tcData ? buildTcfEventCapture(tcData) : null
}
}));
});
});
window.__tcfapi("ping", 2, function (pingData, pingSuccess) { window.__tcfapi("ping", 2, function (pingData, pingSuccess) {
console.log("VendorGet __tcfapi ping:", { console.log("VendorGet __tcfapi ping:", {
@@ -97,44 +151,19 @@
console.log("VendorGet raw event:", tcData); console.log("VendorGet raw event:", tcData);
if (
tcData.eventStatus === "cmpuishown" ||
tcData.eventStatus === "tcloaded"
) {
emitToContentScript(
"tcf_pre_consent_event",
buildTcfEventCapture(tcData)
);
}
if (tcData.eventStatus === "useractioncomplete") { if (tcData.eventStatus === "useractioncomplete") {
const capture = { const capture = buildTcfEventCapture(tcData);
url: window.location.href,
origin: window.location.origin,
timestampUtc: new Date().toISOString(),
cmpId: tcData.cmpId,
cmpVersion: tcData.cmpVersion,
gdprApplies: tcData.gdprApplies,
tcfPolicyVersion: tcData.tcfPolicyVersion,
vendorListVersion: tcData.vendorListVersion,
tcString: tcData.tcString,
eventStatus: tcData.eventStatus,
cmpStatus: tcData.cmpStatus,
isServiceSpecific: tcData.isServiceSpecific,
useNonStandardTexts: tcData.useNonStandardTexts,
publisherCC: tcData.publisherCC,
purposeOneTreatment: tcData.purposeOneTreatment,
purpose: tcData.purpose,
vendor: tcData.vendor,
specialFeatureOptins: tcData.specialFeatureOptins,
publisher: tcData.publisher,
addtlConsent: tcData.addtlConsent,
rawTcData: cloneSerializable(tcData)
};
console.log("VendorGet CONSENT CAPTURE:", capture); console.log("VendorGet CONSENT CAPTURE:", capture);
+41
Datei anzeigen
@@ -123,6 +123,42 @@ h1 {
display: none; display: none;
} }
.pre-consent-capture {
display: grid;
gap: 8px;
margin-bottom: 14px;
padding: 10px;
border: 1px solid #6366f1;
border-radius: 6px;
background: #172033;
}
.pre-consent-capture[hidden] {
display: none;
}
.pre-consent-capture h2 {
margin: 0;
font-size: 13px;
}
.pre-consent-capture p {
margin: 0;
font-size: 12px;
line-height: 1.35;
color: #cbd5e1;
}
.pre-consent-actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.pre-consent-actions button[hidden] {
display: none;
}
.evidence-counts { .evidence-counts {
display: grid; display: grid;
gap: 6px; gap: 6px;
@@ -176,6 +212,11 @@ button:disabled {
opacity: 0.65; opacity: 0.65;
} }
.secondary-workflow {
color: #cbd5e1;
background: #172033;
}
.confirm-modal { .confirm-modal {
position: fixed; position: fixed;
inset: 0; inset: 0;
+49 -9
Datei anzeigen
@@ -3,12 +3,12 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>VendorGet-IV</title> <title>VG-Observe Laufzeitsteuerung</title>
<link rel="stylesheet" href="popup.css"> <link rel="stylesheet" href="popup.css">
</head> </head>
<body> <body>
<main class="popup"> <main class="popup">
<h1>VendorGet-IV</h1> <h1>VG-Observe</h1>
<section class="status" aria-label="Status"> <section class="status" aria-label="Status">
<div class="status-row"> <div class="status-row">
@@ -29,10 +29,35 @@
</div> </div>
</section> </section>
<section class="evidence-retention" aria-label="Evidenzdaten"> <section
<h2>Evidenzdaten</h2> id="pre-consent-capture"
class="pre-consent-capture"
aria-label="Pre-Consent-Erfassung"
hidden
>
<h2>Consent-Vorgang erfassen?</h2>
<p id="pre-consent-capture-summary">
CMP-Vorgang erkannt.
</p>
<div class="pre-consent-actions">
<button id="pre-consent-capture-decline" type="button">
Nein
</button>
<button id="pre-consent-capture-start" type="button">
Ja
</button>
</div>
<div
id="pre-consent-capture-status"
class="retention-status"
aria-live="polite"
></div>
</section>
<section class="evidence-retention" aria-label="Kurzstatus">
<h2>Workspace-Kurzstatus</h2>
<div id="maintenance-warning" class="maintenance-warning" hidden> <div id="maintenance-warning" class="maintenance-warning" hidden>
Aufzeichnung pausiert: Dashboard geöffnet Aufzeichnung pausiert: Observe-Dashboard geöffnet
</div> </div>
<dl class="evidence-counts"> <dl class="evidence-counts">
<div> <div>
@@ -61,15 +86,30 @@
</div> </div>
</dl> </dl>
<button id="evidence-dashboard-button" type="button"> <button id="evidence-dashboard-button" type="button">
Evidence Dashboard öffnen Observe-Dashboard öffnen
</button> </button>
<button id="evidence-export-json-button" type="button"> <button
id="evidence-export-json-button"
class="secondary-workflow"
type="button"
hidden
>
Export Evidence JSON Export Evidence JSON
</button> </button>
<button id="evidence-purge-unlocked-button" type="button"> <button
id="evidence-purge-unlocked-button"
class="secondary-workflow"
type="button"
hidden
>
Ungesperrte Evidence-Daten löschen Ungesperrte Evidence-Daten löschen
</button> </button>
<div id="evidence-export-json-status" class="retention-status" aria-live="polite"></div> <div
id="evidence-export-json-status"
class="retention-status"
aria-live="polite"
hidden
></div>
<div id="evidence-retention-status" class="retention-status" aria-live="polite"> <div id="evidence-retention-status" class="retention-status" aria-live="polite">
Status wird geladen Status wird geladen
</div> </div>
+153
Datei anzeigen
@@ -8,6 +8,19 @@ const requestMonitoringStatus = document.getElementById(
); );
const consentCaptureToggle = document.getElementById("consent-capture-toggle"); const consentCaptureToggle = document.getElementById("consent-capture-toggle");
const consentCaptureStatus = document.getElementById("consent-capture-status"); const consentCaptureStatus = document.getElementById("consent-capture-status");
const preConsentCapture = document.getElementById("pre-consent-capture");
const preConsentCaptureSummary = document.getElementById(
"pre-consent-capture-summary"
);
const preConsentCaptureStart = document.getElementById(
"pre-consent-capture-start"
);
const preConsentCaptureDecline = document.getElementById(
"pre-consent-capture-decline"
);
const preConsentCaptureStatus = document.getElementById(
"pre-consent-capture-status"
);
const maintenanceWarning = document.getElementById("maintenance-warning"); const maintenanceWarning = document.getElementById("maintenance-warning");
const evidenceLockedCount = document.getElementById("evidence-locked-count"); const evidenceLockedCount = document.getElementById("evidence-locked-count");
const evidenceDashboardButton = document.getElementById( const evidenceDashboardButton = document.getElementById(
@@ -47,6 +60,7 @@ const evidenceStoreCountCells = {
document.addEventListener("DOMContentLoaded", async () => { document.addEventListener("DOMContentLoaded", async () => {
await renderSettings(); await renderSettings();
await renderPreConsentCaptureStatus();
await renderEvidenceMaintenanceStatus(); await renderEvidenceMaintenanceStatus();
await renderEvidenceRetentionStatus(); await renderEvidenceRetentionStatus();
@@ -57,7 +71,13 @@ document.addEventListener("DOMContentLoaded", async () => {
"consentCaptureEnabled", "consentCaptureEnabled",
consentCaptureToggle.checked consentCaptureToggle.checked
); );
if (consentCaptureToggle.checked) {
await probePreConsentCaptureForActiveTab();
}
await renderSettings(); await renderSettings();
await renderPreConsentCaptureStatus();
consentCaptureToggle.disabled = false; consentCaptureToggle.disabled = false;
}); });
@@ -84,6 +104,8 @@ document.addEventListener("DOMContentLoaded", async () => {
evidencePurgeUnlockedButton.addEventListener("click", openPurgeConfirmModal); evidencePurgeUnlockedButton.addEventListener("click", openPurgeConfirmModal);
evidencePurgeCancelButton.addEventListener("click", closePurgeConfirmModal); evidencePurgeCancelButton.addEventListener("click", closePurgeConfirmModal);
evidencePurgeConfirmButton.addEventListener("click", purgeUnlockedEvidence); evidencePurgeConfirmButton.addEventListener("click", purgeUnlockedEvidence);
preConsentCaptureStart.addEventListener("click", startPreConsentCapture);
preConsentCaptureDecline.addEventListener("click", declinePreConsentCapture);
}); });
async function renderSettings() { async function renderSettings() {
@@ -158,6 +180,137 @@ function renderEvidenceRetentionMessage(message) {
evidenceRetentionStatus.textContent = message; evidenceRetentionStatus.textContent = message;
} }
async function renderPreConsentCaptureStatus() {
try {
const activeTab = await getActiveTab();
if (!activeTab?.id) {
renderNoPreConsentCapture();
return;
}
const result = await browser.runtime.sendMessage({
type: "get_pre_consent_capture_status",
payload: {
tabId: activeTab.id
}
});
if (!result?.success) {
throw new Error(result?.error ?? "get_pre_consent_capture_status_failed");
}
renderPreConsentCapture(result.capture);
} catch (error) {
renderNoPreConsentCapture();
console.warn("VendorGet-IV pre-consent status failed", error);
}
}
async function probePreConsentCaptureForActiveTab() {
try {
const activeTab = await getActiveTab();
if (!activeTab?.id) {
return;
}
await browser.runtime.sendMessage({
type: "probe_pre_consent_capture_for_tab",
payload: {
tabId: activeTab.id
}
});
} catch (error) {
console.warn("VendorGet-IV pre-consent probe failed", error);
}
}
function renderPreConsentCapture(capture) {
if (!capture) {
renderNoPreConsentCapture();
return;
}
preConsentCapture.hidden = false;
preConsentCaptureStart.hidden = capture.status !== "attention";
preConsentCaptureDecline.hidden = capture.status !== "attention";
if (capture.status === "recording") {
preConsentCaptureSummary.textContent = "Pre-Consent-Erfassung läuft.";
preConsentCaptureStatus.textContent =
"Provider-Announcement wurde gesichert; warte auf useractioncomplete.";
return;
}
preConsentCaptureSummary.textContent = buildPreConsentCaptureSummary(capture);
preConsentCaptureStatus.textContent = "";
}
function renderNoPreConsentCapture() {
preConsentCapture.hidden = true;
preConsentCaptureSummary.textContent = "";
preConsentCaptureStatus.textContent = "";
preConsentCaptureStart.disabled = false;
preConsentCaptureDecline.disabled = false;
}
function buildPreConsentCaptureSummary(capture) {
const eventLabel = capture.firstEventStatus ?? "TCF-Event";
const eventCount = Number(capture.eventCount ?? 0);
return `${eventLabel} erkannt, ${eventCount} Pre-Consent-Event(s) gepuffert.`;
}
async function startPreConsentCapture() {
await answerPreConsentCapture("start_pre_consent_capture");
}
async function declinePreConsentCapture() {
await answerPreConsentCapture("decline_pre_consent_capture");
}
async function answerPreConsentCapture(messageType) {
preConsentCaptureStart.disabled = true;
preConsentCaptureDecline.disabled = true;
try {
const activeTab = await getActiveTab();
if (!activeTab?.id) {
throw new Error("active_tab_unavailable");
}
const result = await browser.runtime.sendMessage({
type: messageType,
payload: {
tabId: activeTab.id
}
});
if (!result?.success) {
throw new Error(result?.error ?? messageType);
}
renderPreConsentCapture(result.capture);
} catch (error) {
preConsentCaptureStatus.textContent = "Aktion konnte nicht ausgeführt werden";
console.warn("VendorGet-IV pre-consent action failed", error);
} finally {
preConsentCaptureStart.disabled = false;
preConsentCaptureDecline.disabled = false;
}
}
async function getActiveTab() {
const tabs = await browser.tabs.query({
active: true,
currentWindow: true
});
return tabs[0] ?? null;
}
function openPurgeConfirmModal() { function openPurgeConfirmModal() {
evidencePurgeConfirmModal.hidden = false; evidencePurgeConfirmModal.hidden = false;
evidencePurgeCancelButton.focus(); evidencePurgeCancelButton.focus();
+8 -6
Datei anzeigen
@@ -3,16 +3,18 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>VG-Observe Request-Explorer</title> <title>VG-Observe Requests prüfen</title>
<link rel="stylesheet" href="request-explorer.css"> <link rel="stylesheet" href="request-explorer.css">
</head> </head>
<body> <body>
<main class="explorer"> <main class="explorer">
<header class="explorer-header"> <header class="explorer-header">
<a class="back-link" href="../dashboard/dashboard.html">Zur&uuml;ck zum Dashboard</a> <a class="back-link" href="../dashboard/dashboard.html">Zur&uuml;ck zum Observe-Dashboard</a>
<h1>Request-Explorer</h1> <h1>Requests prüfen</h1>
<p class="section-help"> <p class="section-help">
Diese Ansicht zeigt zuletzt technisch beobachtete Requests. Diese Ansicht zeigt sichtbar beobachtete Browser-Requests aus der
Laufzeitbeobachtung. Sie dokumentiert technische Vorgänge und nimmt
keine rechtliche Bewertung vor.
</p> </p>
<dl class="request-overview" aria-label="Geladene Request-Uebersicht"> <dl class="request-overview" aria-label="Geladene Request-Uebersicht">
<div> <div>
@@ -27,7 +29,7 @@
</header> </header>
<section class="panel" aria-labelledby="request-list-title"> <section class="panel" aria-labelledby="request-list-title">
<h2 id="request-list-title">Zuletzt beobachtete Requests</h2> <h2 id="request-list-title">Technisch beobachtete Requests</h2>
<p id="request-empty" class="empty-state" hidden> <p id="request-empty" class="empty-state" hidden>
Keine beobachteten Requests vorhanden. Keine beobachteten Requests vorhanden.
</p> </p>
@@ -35,7 +37,7 @@
<div id="request-groups" class="request-groups"></div> <div id="request-groups" class="request-groups"></div>
<section class="request-detail" aria-labelledby="request-detail-title"> <section class="request-detail" aria-labelledby="request-detail-title">
<h2 id="request-detail-title">Ausgew&auml;hlter Request</h2> <h2 id="request-detail-title">Ausgew&auml;hlter beobachteter Request</h2>
<div id="request-detail-summary"></div> <div id="request-detail-summary"></div>
</section> </section>
+32
Datei anzeigen
@@ -0,0 +1,32 @@
{
"folders": [
{
"name": "VG-Consent",
"path": ".."
}
],
"settings": {
"codex.projectRole": "Consent-Workspace",
"codex.scope": [
"Consent-Capture",
"Consent-State-Rekonstruktion",
"Consent-Events",
"Consent-Explorer",
"Evidence-Chain-Resolver",
"lesende Request-Korrelation",
"GVL nur als Referenz/Vault"
],
"codex.outOfScope": [
"allgemeiner Cleanup",
"GVL-Import",
"GVL-Export",
"GVL-Verify",
"Dashboard-Refactoring",
"Request-Explorer-Ausbau",
"neue Datenquellen",
"IndexedDB-Migrationen",
"Analyse-Engine"
],
"codex.primaryQuestion": "Hilft diese Änderung dabei, die technisch beobachtbare Wirkung einer konkreten Consent-Entscheidung nachvollziehbar, reproduzierbar und evidenzorientiert zu rekonstruieren?"
}
}