Datenschutz Weg B nach Prüfung: Zusagen auf den Dienst begrenzt, [OFFEN]-Markierungen, Upload-Sperren

Abschnitt 11: Sperre und Kürzung ab dem Lesen unserer Antwort, nur je Beitrag und E-Mail-Adresse (neues Gesuch = neuer Beitrag);
aussortierte Anfragen 30 Tage; feste Ausschlussliste der Websuche samt robots.txt; Kriterium für die Dauer der Reste; Kopie der
Standardvertragsklauseln; Hinweis in der ersten Nachricht nennt den Widerspruch; Abschnitt 11 und 14: die KI sortiert aus, ein
Mitarbeiter entscheidet über jede Antwort. Alarm-Postfach-Anbieter und Aufbewahrung der Server-Sicherungen stehen als sichtbare
[OFFEN]-Markierung im Text (auch in Abschnitt 12).

Tests: Pins angepasst, Markierungen nur die zwei erlaubten, neuer Test für Zusagen in Reichweite des Dienstes. Upload-Skript bricht ab,
solange eine [OFFEN]-Markierung steht oder die Prüfsumme neben dem PDF nicht zur HTML passt.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-22 13:57:18 +00:00
1 parent b3ca84a8e5
commit 23ab43f9fe
4 files changed
+100 -23

No files matched your search

+9 -8
View File
@@ -145,17 +145,17 @@
<a id="interessenten"></a> <a id="interessenten"></a>
<h2>11. Interessenten: Antworten auf öffentliche Anfragen</h2> <h2>11. Interessenten: Antworten auf öffentliche Anfragen</h2>
<p>Manche Menschen und Betriebe suchen öffentlich im Netz jemanden, der ihnen eine Website baut, zum Beispiel in einer Kleinanzeige, auf einem Portal für Freelancer oder in einem Frage-Forum. Auf solche Gesuche melden wir uns von uns aus nur einmal und bieten unsere Leistungen an. Weil wir Ihre Daten dabei nicht bei Ihnen erheben, sondern aus Ihrem öffentlichen Beitrag, informieren wir Sie hier und in unserer ersten Nachricht darüber (Art. 14 DSGVO).</p> <p>Manche Menschen und Betriebe suchen öffentlich im Netz jemanden, der ihnen eine Website baut, zum Beispiel in einer Kleinanzeige, auf einem Portal für Freelancer oder in einem Frage-Forum. Auf solche Gesuche melden wir uns von uns aus nur einmal und bieten unsere Leistungen an. Weil wir Ihre Daten dabei nicht bei Ihnen erheben, sondern aus Ihrem öffentlichen Beitrag, informieren wir Sie hier und in unserer ersten Nachricht darüber (Art. 14 DSGVO).</p>
<p>So läuft es ab: Ein KI-Assistent (das Sprachmodell Claude von Anthropic) bewertet, ob die Anfrage zu unseren Leistungen passt, und entwirft eine Antwort. Ein Mitarbeiter von WEBklar liest den Entwurf, ändert ihn bei Bedarf und sendet ihn einmal von Hand: über die Plattform, auf der Sie gefragt haben, oder per E-Mail, wenn Ihr Beitrag darum bittet. Automatische E-Mails verschicken wir nicht. Für E-Mails gilt Abschnitt 8. In unserer Nachricht steht, dass eine KI-Assistentin von WEBklar antwortet, wie lange wir Ihre Angaben speichern und wo Sie diesen Abschnitt finden. Passt Ihre Anfrage nicht zu uns, schreiben wir Ihnen nicht; die Daten speichern wir dann trotzdem bis zur Kürzung (siehe unten), damit wir den Beitrag nicht noch einmal bewerten.</p> <p>So läuft es ab: Ein KI-Assistent (das Sprachmodell Claude von Anthropic) bewertet, ob die Anfrage zu unseren Leistungen passt, und entwirft eine Antwort. Ein Mitarbeiter von WEBklar liest den Entwurf, ändert ihn bei Bedarf und sendet ihn einmal von Hand: über die Plattform, auf der Sie gefragt haben, oder per E-Mail, wenn Ihr Beitrag darum bittet. Automatische E-Mails verschicken wir nicht. Für E-Mails gilt Abschnitt 8. In unserer ersten Nachricht steht, dass eine KI-Assistentin von WEBklar antwortet, wie lange wir die Angaben aus Ihrer Anfrage in unserer Datenbank speichern, dass Sie widersprechen können, und wo Sie diesen Abschnitt finden. Passt Ihre Anfrage nach der Bewertung der KI nicht zu uns, schreiben wir Ihnen nicht und kürzen die Daten innerhalb von 30 Tagen nach dem Fund (siehe unten); bis dahin speichern wir sie, damit wir den Beitrag nicht noch einmal bewerten.</p>
<p>Die Gesuche finden wir auf zwei Wegen. Erstens schicken uns Plattformen auf unseren Suchauftrag Benachrichtigungs-E-Mails (derzeit Kleinanzeigen, freelancermap und freelance.de); diese Plattformen rufen wir nicht selbst ab. Zweitens findet ein KI-Assistent mit einer Websuche öffentliche Beiträge im Netz und in Frage-Foren, derzeit in den Foren gutefrage.net, forum-hilfe.de, wer-weiss-was.de und wpde.org sowie auf anderen öffentlichen Webseiten. Die Suche und den Abruf der Seiten führt Anthropic aus. Von der Suche ausgenommen sind Plattformen, die automatisierte Zugriffe untersagen, derzeit unter anderem Facebook, Instagram, LinkedIn, Xing, Reddit, Upwork, Fiverr und MyHammer. Wir suchen nach Gesuchen, nicht nach bestimmten Personen.</p> <p>Die Gesuche finden wir auf zwei Wegen. Erstens schicken uns Plattformen auf unseren Suchauftrag Benachrichtigungs-E-Mails (derzeit Kleinanzeigen, freelancermap und freelance.de); diese Plattformen rufen wir nicht selbst ab. Zweitens findet ein KI-Assistent mit einer Websuche öffentliche Beiträge im Netz und in Frage-Foren, derzeit in den Foren gutefrage.net, forum-hilfe.de, wer-weiss-was.de und wpde.org sowie auf anderen öffentlichen Webseiten. Die Suche und den Abruf der Seiten führt Anthropic aus. Von der Websuche und vom Abruf ausgenommen sind die Plattformen Kleinanzeigen, freelancermap, freelance.de, Facebook, Instagram, LinkedIn, Xing, Reddit, Upwork, Fiverr und MyHammer sowie Seiten, deren robots.txt den Abruf sperrt. Wir suchen nach Gesuchen, nicht nach bestimmten Personen.</p>
<p>Wir speichern den Titel des Beitrags, den Beitragstext (höchstens 4000 Zeichen), den Link, das Datum, Ort und Postleitzahl, wenn sie genannt sind, die Plattform, eine Punktzahl mit kurzer Begründung und, wenn der Beitrag um eine Antwort per E-Mail bittet, die E-Mail-Adresse aus dem Beitrag. Ein eigenes Feld für Ihren Namen gibt es nicht; steht Ihr Name im Titel oder im Text, speichern wir ihn damit. Telefonnummern entfernen wir beim Speichern automatisch aus dem Text; das folgt einer Faustregel und erkennt nicht jede Schreibweise. Dazu kommen der Entwurf unserer Antwort, die gesendete Nachricht mit Text und Zeitpunkt und ein Protokoll der einzelnen Arbeitsschritte, zum Beispiel „Neue Anfrage: Titel des Beitrags“.</p> <p>Wir speichern den Titel des Beitrags, den Beitragstext (höchstens 4000 Zeichen), den Link, das Datum, Ort und Postleitzahl, wenn sie genannt sind, die Plattform, eine Punktzahl mit kurzer Begründung und, wenn der Beitrag um eine Antwort per E-Mail bittet, die E-Mail-Adresse aus dem Beitrag. Ein eigenes Feld für Ihren Namen gibt es nicht; steht Ihr Name im Titel oder im Text, speichern wir ihn damit. Telefonnummern entfernen wir beim Speichern automatisch aus dem Text; das folgt einer Faustregel und erkennt nicht jede Schreibweise. Dazu kommen der Entwurf unserer Antwort, die gesendete Nachricht mit Text und Zeitpunkt und ein Protokoll der einzelnen Arbeitsschritte, zum Beispiel „Neue Anfrage: Titel des Beitrags“.</p>
<p>Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO. Unser berechtigtes Interesse ist, auf öffentliche Gesuche nach Website-Leistungen zu antworten. Wir melden uns je Anfrage von uns aus nur einmal, und Sie können der Verarbeitung jederzeit widersprechen.</p> <p>Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO. Unser berechtigtes Interesse ist, auf öffentliche Gesuche nach Website-Leistungen zu antworten. Wir melden uns je Anfrage von uns aus nur einmal, und Sie können der Verarbeitung jederzeit widersprechen.</p>
<p>Wir speichern die Daten auf unserem Server bei der Hetzner Online GmbH in Deutschland (Abschnitt 3). Für die Bewertung, den Entwurf und die Websuche setzen wir das Sprachmodell Claude der Anthropic Ireland, Limited, Dublin, Irland, als Auftragsverarbeiter ein. Anthropic erhält dafür den Titel, den Text und den Ort des Beitrags sowie bei der Websuche unsere Suchaufträge und die gefundenen Seiten. Dabei können Daten in die USA übermittelt werden, abgesichert durch Standardvertragsklauseln (Art. 46 Abs. 2 lit. c DSGVO).</p> <p>Wir speichern die Daten auf unserem Server bei der Hetzner Online GmbH in Deutschland (Abschnitt 3). Für die Bewertung, den Entwurf und die Websuche setzen wir das Sprachmodell Claude der Anthropic Ireland, Limited, Dublin, Irland, als Auftragsverarbeiter ein. Anthropic erhält dafür den Titel, den Text und den Ort des Beitrags sowie bei der Websuche unsere Suchaufträge und die gefundenen Seiten. Dabei können Daten in die USA übermittelt werden, abgesichert durch Standardvertragsklauseln (Art. 46 Abs. 2 lit. c DSGVO); eine Kopie erhalten Sie bei uns (support@webklar.com).</p>
<p>Die Bewertung der KI entscheidet nicht allein: Sie bereitet nur einen Entwurf vor. Ob und was wir Ihnen schreiben, entscheidet ein Mitarbeiter (Abschnitt 14).</p> <p>Die KI sortiert Anfragen aus, die nicht zu uns passen; auf diese schreiben wir nicht. Ob und was wir einer passenden Anfrage antworten, entscheidet ein Mitarbeiter (Abschnitt 14).</p>
<p>Wie lange wir die Daten speichern: Wir kürzen Ihre Daten spätestens 90 Tage nach dem Fund. Sagen Sie uns, dass Sie kein Interesse haben, kürzen wir sie innerhalb von 30 Tagen; widersprechen Sie, sperren und kürzen wir sie sofort. Kürzen heißt: Wir löschen Titel, Beitragstext, Link, E-Mail-Adresse, Entwurf und unsere Nachricht an Sie und bereinigen die zugehörigen Einträge im Protokoll. Übrig bleiben nur pseudonymisierte Prüfwerte (Hash-Werte) des Beitrags und gegebenenfalls Ihrer E-Mail-Adresse sowie Ort, Plattform, Punktzahl, Status und die Zeitpunkte von Beitrag und Fund, jedoch ohne Titel, Text, Link und Kontaktdaten. Ein Hash-Wert ist ein Prüfwert, aus dem sich der ursprüngliche Inhalt nicht ablesen lässt; wir erkennen damit nur, wenn derselbe Beitrag oder dieselbe E-Mail-Adresse noch einmal auftaucht, und schreiben Ihnen dann nicht noch einmal. Haben Sie uns geantwortet und übernehmen wir Ihre Angaben als Kontakt oder Kunde in unsere Kundenverwaltung, gelten für diese Daten stattdessen Abschnitt 5 und, wenn ein Vertrag zustande kommt, die Abschnitte 7 und 9.</p> <p>Wie lange wir die Daten speichern: Wir kürzen Ihre Daten spätestens 90 Tage nach dem Fund. Passt Ihre Anfrage nicht zu uns, kürzen wir sie innerhalb von 30 Tagen nach dem Fund. Sagen Sie uns, dass Sie kein Interesse haben, kürzen wir sie innerhalb von 30 Tagen, nachdem wir Ihre Antwort gelesen haben. Widersprechen Sie, sperren und kürzen wir sie sofort, sobald wir Ihren Widerspruch lesen. Kürzen heißt: Wir löschen Titel, Beitragstext, Link, E-Mail-Adresse, Entwurf und unsere Nachricht an Sie und bereinigen die zugehörigen Einträge im Protokoll. Übrig bleiben nur pseudonymisierte Prüfwerte (Hash-Werte) des Beitrags und gegebenenfalls Ihrer E-Mail-Adresse sowie Ort, Plattform, Punktzahl, Status und die Zeitpunkte von Beitrag und Fund, jedoch ohne Titel, Text, Link und Kontaktdaten. Ein Hash-Wert ist ein Prüfwert, aus dem sich der ursprüngliche Inhalt nicht ablesen lässt; wir erkennen damit nur, wenn derselbe Beitrag oder dieselbe E-Mail-Adresse noch einmal auftaucht, und schreiben Ihnen dann nicht noch einmal. Diese Prüfwerte und die übrigen Angaben bewahren wir so lange auf, wie wir verhindern müssen, dass wir Sie wegen desselben Beitrags oder derselben E-Mail-Adresse erneut ansprechen. Haben Sie uns geantwortet und übernehmen wir Ihre Angaben als Kontakt oder Kunde in unsere Kundenverwaltung, gelten für diese Daten stattdessen Abschnitt 5 und, wenn ein Vertrag zustande kommt, die Abschnitte 7 und 9.</p>
<p>Für Kopien außerhalb der Datenbank gilt Folgendes. Die Benachrichtigungs-E-Mails der Plattformen enthalten die Anzeigentexte und liegen in einem eigenen E-Mail-Postfach; eine Postfach-Regel löscht sie dort spätestens nach 90 Tagen. Eine E-Mail, die ein Mitarbeiter Ihnen sendet, liegt außerdem in unserem Postfach; dafür gilt Abschnitt 8 und, soweit die E-Mail ein Geschäftsbrief ist, die Frist aus Abschnitt 9. In den Datensicherungen unseres Servers (Abschnitt 12) können gekürzte Daten noch kurze Zeit enthalten sein, bis die Sicherungen überschrieben werden. Die Antwort, die wir auf einer Plattform abschicken, sehen auch deren Betreiber; dafür gelten dessen Datenschutzhinweise.</p> <p>Für Kopien außerhalb der Datenbank gilt Folgendes. Die Benachrichtigungs-E-Mails der Plattformen enthalten die Anzeigentexte und liegen in einem eigenen E-Mail-Postfach bei <span class="offen">[OFFEN: Anbieter Alarm-Postfach]</span>; eine Postfach-Regel löscht sie dort spätestens nach 90 Tagen. Eine E-Mail, die ein Mitarbeiter Ihnen sendet, liegt außerdem in unserem Postfach; dafür gilt Abschnitt 8 und, soweit die E-Mail ein Geschäftsbrief ist, die Frist aus Abschnitt 9. In den Datensicherungen unseres Servers (Abschnitt 12) können gekürzte Daten noch enthalten sein: <span class="offen">[OFFEN: Aufbewahrung der Server-Sicherungen]</span>. Die Antwort, die wir auf einer Plattform abschicken, sehen auch deren Betreiber; dafür gelten dessen Datenschutzhinweise.</p>
<div class="box widerspruch"> <div class="box widerspruch">
<strong>Ihr Widerspruchsrecht gegen diese Verarbeitung (Art. 21 DSGVO)</strong> <strong>Ihr Widerspruchsrecht gegen diese Verarbeitung (Art. 21 DSGVO)</strong>
<p>Sie können der Verarbeitung Ihrer Daten jederzeit widersprechen, ohne Gründe anzugeben. Antworten Sie dazu auf unsere Nachricht oder schreiben Sie an support@webklar.com. Wir sperren Ihre Daten dann sofort, schreiben Ihnen nicht mehr und kürzen sie wie oben beschrieben. Weitere Rechte (Art. 15 bis 20 DSGVO) und das Beschwerderecht beschreibt Abschnitt 13.</p> <p>Sie können der Verarbeitung Ihrer Daten jederzeit widersprechen, ohne Gründe anzugeben. Antworten Sie dazu auf unsere Nachricht oder schreiben Sie an support@webklar.com. Sobald wir Ihre Nachricht lesen, sperren wir den Beitrag und, soweit wir sie gespeichert haben, Ihre E-Mail-Adresse, schreiben Ihnen dazu nicht mehr und kürzen Ihre Daten wie oben beschrieben. Wir erkennen Sie dabei nur am Beitrag und an der E-Mail-Adresse: Ein neues Gesuch, das Sie später einstellen, ist für uns ein neuer Beitrag; sagen Sie uns dann bitte noch einmal Bescheid. Weitere Rechte (Art. 15 bis 20 DSGVO) und das Beschwerderecht beschreibt Abschnitt 13.</p>
</div> </div>
<h2>12. Empfänger im Überblick</h2> <h2>12. Empfänger im Überblick</h2>
@@ -164,6 +164,7 @@
<li>UptimeRobot s. r. o.: Überwachung der Erreichbarkeit, Daten auch außerhalb der EU (insbesondere USA), Auftragsverarbeiter (Abschnitt 3)</li> <li>UptimeRobot s. r. o.: Überwachung der Erreichbarkeit, Daten auch außerhalb der EU (insbesondere USA), Auftragsverarbeiter (Abschnitt 3)</li>
<li>Stripe: Zahlungsabwicklung und Eröffnung des Stripe-Kontos für den Webshop und den Buchungskalender unserer Kunden (Abschnitte 6d, 6f und 7)</li> <li>Stripe: Zahlungsabwicklung und Eröffnung des Stripe-Kontos für den Webshop und den Buchungskalender unserer Kunden (Abschnitte 6d, 6f und 7)</li>
<li>Anthropic Ireland, Limited: Bewertung öffentlicher Anfragen, Entwurf von Antworten sowie Websuche und Seitenabruf durch ein Sprachmodell (Auftragsverarbeiter, Daten auch in den USA, Abschnitt 11)</li> <li>Anthropic Ireland, Limited: Bewertung öffentlicher Anfragen, Entwurf von Antworten sowie Websuche und Seitenabruf durch ein Sprachmodell (Auftragsverarbeiter, Daten auch in den USA, Abschnitt 11)</li>
<li><span class="offen">[OFFEN: Anbieter Alarm-Postfach]</span>: E-Mail-Postfach für die Benachrichtigungen der Plattformen (Abschnitt 11)</li>
<li>Plattformen, auf denen wir Ihre Anfrage beantworten, zum Beispiel Kleinanzeigen: Unsere Antwort läuft über deren Dienst (Abschnitt 11)</li> <li>Plattformen, auf denen wir Ihre Anfrage beantworten, zum Beispiel Kleinanzeigen: Unsere Antwort läuft über deren Dienst (Abschnitt 11)</li>
<li>Porkbun LLC: bisheriger E-Mail-Dienst, nur noch bis zur Löschung älterer E-Mails (Abschnitt 8)</li> <li>Porkbun LLC: bisheriger E-Mail-Dienst, nur noch bis zur Löschung älterer E-Mails (Abschnitt 8)</li>
<li>Domain-Registrar und Vergabestellen, sofern wir eine Domain für Sie registrieren (Abschnitt 10)</li> <li>Domain-Registrar und Vergabestellen, sofern wir eine Domain für Sie registrieren (Abschnitt 10)</li>
@@ -180,7 +181,7 @@
<p>Sie können sich außerdem bei einer Datenschutz-Aufsichtsbehörde beschweren. Für uns zuständig ist der Landesbeauftragte für den Datenschutz und die Informationsfreiheit Rheinland-Pfalz, Hintere Bleiche 34, 55116 Mainz, poststelle@datenschutz.rlp.de.</p> <p>Sie können sich außerdem bei einer Datenschutz-Aufsichtsbehörde beschweren. Für uns zuständig ist der Landesbeauftragte für den Datenschutz und die Informationsfreiheit Rheinland-Pfalz, Hintere Bleiche 34, 55116 Mainz, poststelle@datenschutz.rlp.de.</p>
<h2>14. Pflicht zur Bereitstellung, keine automatisierten Einzelentscheidungen nach Art. 22 DSGVO</h2> <h2>14. Pflicht zur Bereitstellung, keine automatisierten Einzelentscheidungen nach Art. 22 DSGVO</h2>
<p>Für einen Vertrag mit uns brauchen wir die dafür nötigen Angaben. Ohne sie können wir den Vertrag nicht schließen. Darüber hinaus sind Sie nicht verpflichtet, uns Daten bereitzustellen. Entscheidungen, die Ihnen gegenüber rechtliche Wirkung entfalten oder Sie in ähnlicher Weise erheblich beeinträchtigen (Art. 22 DSGVO), treffen wir nicht automatisiert. Die Bewertung öffentlicher Anfragen durch einen KI-Assistenten (Abschnitt 11) bereitet nur eine Antwort vor; ob und was wir antworten, entscheidet ein Mitarbeiter, der die Antwort von Hand abschickt.</p> <p>Für einen Vertrag mit uns brauchen wir die dafür nötigen Angaben. Ohne sie können wir den Vertrag nicht schließen. Darüber hinaus sind Sie nicht verpflichtet, uns Daten bereitzustellen. Entscheidungen, die Ihnen gegenüber rechtliche Wirkung entfalten oder Sie in ähnlicher Weise erheblich beeinträchtigen (Art. 22 DSGVO), treffen wir nicht automatisiert. Die Bewertung öffentlicher Anfragen durch einen KI-Assistenten (Abschnitt 11) sortiert Anfragen aus, die nicht zu uns passen; auf diese schreiben wir nicht. Für alle anderen bereitet sie nur eine Antwort vor; ob und was wir antworten, entscheidet ein Mitarbeiter, der die Antwort von Hand abschickt.</p>
<h2>15. Änderungen</h2> <h2>15. Änderungen</h2>
<p>Wir passen diese Erklärung an, wenn sich unsere Verarbeitung oder die Rechtslage ändert. Es gilt die jeweils hier veröffentlichte Fassung.</p> <p>Wir passen diese Erklärung an, wenn sich unsere Verarbeitung oder die Rechtslage ändert. Es gilt die jeweils hier veröffentlichte Fassung.</p>
+19 -1
View File
@@ -3,6 +3,9 @@
// legal-files, Collection legalVersions): neue Fassung "current", die bisherige gültige wird "superseded" mit validTo. Nur mit // legal-files, Collection legalVersions): neue Fassung "current", die bisherige gültige wird "superseded" mit validTo. Nur mit
// --freigabe=JJJJ-MM-TT; dieser Tag muss der „Stand TT.MM.JJJJ“ in public/datenschutz.html entsprechen. Schon hochgeladene Stände (gleiche source, // --freigabe=JJJJ-MM-TT; dieser Tag muss der „Stand TT.MM.JJJJ“ in public/datenschutz.html entsprechen. Schon hochgeladene Stände (gleiche source,
// also gleicher Commit) werden übersprungen. Den Ablauf teilt sich das Skript mit rechtliches-hochladen.mjs (Funktion hochladen). // also gleicher Commit) werden übersprungen. Den Ablauf teilt sich das Skript mit rechtliches-hochladen.mjs (Funktion hochladen).
// Zwei Sperren gegen eine unfertige Fassung (vor dem Schreiben): (1) steht im Text noch eine „[OFFEN: …]“-Markierung (offene Tatsache im
// Faktenblatt), (2) passt die Prüfsumme neben dem PDF (docs/WEBklar-Datenschutzerklaerung.pdf.sha256) nicht zur HTML, ist das PDF also nicht aus
// dieser HTML gedruckt (scripts/build-legal-pdfs.sh datenschutz), dann Abbruch.
// //
// node scripts/datenschutz-hochladen.mjs --probe nur lesen: Schlüssel, Bucket, Karte datenschutz und ihre Versionen // node scripts/datenschutz-hochladen.mjs --probe nur lesen: Schlüssel, Bucket, Karte datenschutz und ihre Versionen
// node scripts/datenschutz-hochladen.mjs --freigabe=JJJJ-MM-TT lädt die Datenschutzerklärung hoch (validFrom = Freigabedatum); ohne Karte, // node scripts/datenschutz-hochladen.mjs --freigabe=JJJJ-MM-TT lädt die Datenschutzerklärung hoch (validFrom = Freigabedatum); ohne Karte,
@@ -12,6 +15,7 @@
// Schlüssel: kurzlebiger Appwrite-Schlüssel mit files.read/files.write/documents.read/documents.write in /root/.appwrite-rechtliches.key (chmod 600). // Schlüssel: kurzlebiger Appwrite-Schlüssel mit files.read/files.write/documents.read/documents.write in /root/.appwrite-rechtliches.key (chmod 600).
// Der Import dieser Datei liest weder den Schlüssel noch nutzt er das Netz; beides geschieht nur in main (deps.schluessel, deps.fetch). // Der Import dieser Datei liest weder den Schlüssel noch nutzt er das Netz; beides geschieht nur in main (deps.schluessel, deps.fetch).
// Dieses Skript wird nur geschrieben und mit Nachbau-deps getestet, nie von einem Agenten ausgeführt (auch nicht mit --probe). // Dieses Skript wird nur geschrieben und mit Nachbau-deps getestet, nie von einem Agenten ausgeführt (auch nicht mit --probe).
import { createHash } from 'node:crypto'
import { readFileSync } from 'node:fs' import { readFileSync } from 'node:fs'
import path from 'node:path' import path from 'node:path'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
@@ -21,6 +25,8 @@ const DOKUMENT_KEY = 'datenschutz'
/** [PDF, HTML]: in dieser Reihenfolge hochgeladen. */ /** [PDF, HTML]: in dieser Reihenfolge hochgeladen. */
const DATEIEN = Object.freeze(['docs/WEBklar-Datenschutzerklaerung.pdf', 'public/datenschutz.html']) const DATEIEN = Object.freeze(['docs/WEBklar-Datenschutzerklaerung.pdf', 'public/datenschutz.html'])
const QUELLE = 'public/datenschutz.html' const QUELLE = 'public/datenschutz.html'
/** Neben dem PDF: SHA-256 der HTML, aus der es gedruckt wurde (scripts/build-legal-pdfs.sh datenschutz). */
const PRUEFSUMME = 'docs/WEBklar-Datenschutzerklaerung.pdf.sha256'
/** Für Kunden sichtbar (WOMS „Rechtliches“): darum weder „Workers“ noch „Hustler“. */ /** Für Kunden sichtbar (WOMS „Rechtliches“): darum weder „Workers“ noch „Hustler“. */
const AENDERUNG = 'Abschnitt 11 Interessenten (Antworten auf öffentliche Anfragen)' const AENDERUNG = 'Abschnitt 11 Interessenten (Antworten auf öffentliche Anfragen)'
@@ -71,9 +77,21 @@ export async function main(argv, deps = {}) {
freigabe, freigabe,
kartenHinweis: '(die Karte gehört zu WOMS „Rechtliches“; dieses Skript legt keine Karten an)', kartenHinweis: '(die Karte gehört zu WOMS „Rechtliches“; dieses Skript legt keine Karten an)',
pruefeInhalt: (lies) => { pruefeInhalt: (lies) => {
const stand = standIn(lies(QUELLE).toString('utf8')) const html = lies(QUELLE)
const stand = standIn(html.toString('utf8'))
if (!stand) return `Abbruch: „Stand TT.MM.JJJJ“ fehlt in ${QUELLE}` if (!stand) return `Abbruch: „Stand TT.MM.JJJJ“ fehlt in ${QUELLE}`
if (stand !== ddmmjjjj(freigabe)) return `Abbruch: Stand ${stand} in ${QUELLE} passt nicht zur Freigabe ${ddmmjjjj(freigabe)}` if (stand !== ddmmjjjj(freigabe)) return `Abbruch: Stand ${stand} in ${QUELLE} passt nicht zur Freigabe ${ddmmjjjj(freigabe)}`
// offene Tatsachen sind kein veröffentlichungsfähiger Text: die Markierung zuerst durch die geklärte Angabe ersetzen
const offen = [...new Set(html.toString('utf8').replace(/<!--[\s\S]*?-->/g, '').match(/\[OFFEN[^\]]*\]/g) ?? [])]
if (offen.length > 0) return `Abbruch: offene Markierung im Text von ${QUELLE}: ${offen.join(', ')} (erst klären und ersetzen, dann Stand/Pin setzen, PDF drucken, pushen)`
// das PDF muss aus genau dieser HTML gedruckt sein
let summe = ''
try {
summe = lies(PRUEFSUMME).toString('utf8').trim()
} catch {
return `Abbruch: ${PRUEFSUMME} fehlt oder ist nicht lesbar (PDF mit scripts/build-legal-pdfs.sh datenschutz drucken)`
}
if (summe !== createHash('sha256').update(html).digest('hex')) return `Abbruch: das PDF ist nicht aus dieser HTML gedruckt (Prüfsumme in ${PRUEFSUMME} passt nicht zu ${QUELLE}); scripts/build-legal-pdfs.sh datenschutz`
return null return null
}, },
}, deps) }, deps)
+33 -3
View File
@@ -1,5 +1,6 @@
import { test } from 'node:test' import { test } from 'node:test'
import assert from 'node:assert/strict' import assert from 'node:assert/strict'
import { createHash } from 'node:crypto'
// Hochladen der Datenschutzerklärung nach WOMS "Rechtliches" (scripts/datenschutz-hochladen.mjs): die Tests rufen main nur mit Nachbau-deps // Hochladen der Datenschutzerklärung nach WOMS "Rechtliches" (scripts/datenschutz-hochladen.mjs): die Tests rufen main nur mit Nachbau-deps
// auf, nie mit dem echten Schlüssel oder dem Netz. Das globale fetch wirft und zählt, damit ein Weg am Nachbau vorbei auffällt. // auf, nie mit dem echten Schlüssel oder dem Netz. Das globale fetch wirft und zählt, damit ein Weg am Nachbau vorbei auffällt.
@@ -13,7 +14,8 @@ const m = await import('../scripts/datenschutz-hochladen.mjs') // der Import lie
const frueher = ['v1', 'v2', 'v3', 'v4', 'v5'].map((v, i) => ({ $id: `s${i}`, version: v, validFrom: `2026-09-0${i + 1}`, status: 'superseded', source: `x${i}` })) const frueher = ['v1', 'v2', 'v3', 'v4', 'v5'].map((v, i) => ({ $id: `s${i}`, version: v, validFrom: `2026-09-0${i + 1}`, status: 'superseded', source: `x${i}` }))
const AKTUELL = { $id: 'alt', version: 'v6', validFrom: '2026-10-05', status: 'current', source: 'kundenbereich@aaaaaaa:public/datenschutz.html' } const AKTUELL = { $id: 'alt', version: 'v6', validFrom: '2026-10-05', status: 'current', source: 'kundenbereich@aaaaaaa:public/datenschutz.html' }
// stand = das Datum, das im Nachbau der HTML-Datei hinter "Stand" steht (die PDF-Datei ist beliebiger Inhalt) // stand = das Datum, das im Nachbau der HTML-Datei hinter "Stand" steht (die PDF-Datei ist beliebiger Inhalt)
function backend({ versionen = [...frueher, AKTUELL], karten = [{ $id: 'datenschutz', key: 'datenschutz' }], gitStatus = '', stand = '20.10.2026', vorhanden = () => true } = {}) { // zusatz = zusätzlicher Inhalt der HTML (zum Beispiel eine offene Markierung); summe = Inhalt der .sha256 (null = passend zur HTML, 'fehlt' = nicht lesbar)
function backend({ versionen = [...frueher, AKTUELL], karten = [{ $id: 'datenschutz', key: 'datenschutz' }], gitStatus = '', stand = '20.10.2026', vorhanden = () => true, zusatz = '', summe = null } = {}) {
const aufrufe = [], log = [] const aufrufe = [], log = []
const antwort = (obj) => ({ ok: true, status: 200, text: async () => JSON.stringify(obj) }) const antwort = (obj) => ({ ok: true, status: 200, text: async () => JSON.stringify(obj) })
const fetch = async (url, opt = {}) => { const fetch = async (url, opt = {}) => {
@@ -27,8 +29,12 @@ function backend({ versionen = [...frueher, AKTUELL], karten = [{ $id: 'datensch
if (methode === 'PATCH' && pfad.includes('/collections/legalVersions/documents/')) return antwort({}) if (methode === 'PATCH' && pfad.includes('/collections/legalVersions/documents/')) return antwort({})
throw new Error(`unerwartet: ${methode} ${pfad}`) throw new Error(`unerwartet: ${methode} ${pfad}`)
} }
const html = () => Buffer.from(stand === null ? '<p class="meta">ohne Datum</p>' : `<p class="meta">Stand ${stand}</p>`) const html = () => Buffer.from((stand === null ? '<p class="meta">ohne Datum</p>' : `<p class="meta">Stand ${stand}</p>`) + zusatz)
return { aufrufe, log, deps: { fetch, schluessel: () => 'test-schluessel', log: (z) => log.push(z), git: (args) => (args[0] === 'status' ? gitStatus : 'abc1234\n'), dateien: { existsSync: vorhanden, readFileSync: (rel) => (rel.endsWith('.html') ? html() : Buffer.from('x')) } } } const pruefsumme = () => {
if (summe === 'fehlt') throw new Error('ENOENT')
return Buffer.from(`${summe ?? createHash('sha256').update(html()).digest('hex')}\n`)
}
return { aufrufe, log, deps: { fetch, schluessel: () => 'test-schluessel', log: (z) => log.push(z), git: (args) => (args[0] === 'status' ? gitStatus : 'abc1234\n'), dateien: { existsSync: vorhanden, readFileSync: (rel) => (rel.endsWith('.html') ? html() : rel.endsWith('.sha256') ? pruefsumme() : Buffer.from('x')) } } }
} }
const schreibend = (b) => b.aufrufe.filter((a) => a.methode !== 'GET') const schreibend = (b) => b.aufrufe.filter((a) => a.methode !== 'GET')
@@ -91,6 +97,30 @@ test('Stand in der HTML muss der Freigabe entsprechen, sonst Abbruch vor jedem S
const richtig = backend({ stand: '21.10.2026' }) const richtig = backend({ stand: '21.10.2026' })
assert.equal(await m.main(['--freigabe=2026-10-21'], richtig.deps), 0, 'mit passender Freigabe geht derselbe Stand durch') assert.equal(await m.main(['--freigabe=2026-10-21'], richtig.deps), 0, 'mit passender Freigabe geht derselbe Stand durch')
}) })
test('offene Markierung im Text: Abbruch vor jedem Schreiben, mit Nennung der Markierung; Kommentare zählen nicht', async () => {
const offen = backend({ zusatz: '<p>bei <span class="offen">[OFFEN: Anbieter Alarm-Postfach]</span></p>' })
assert.equal(await m.main(['--freigabe=2026-10-20'], offen.deps), 1)
assert.ok(offen.log.some((z) => z.includes('offene Markierung') && z.includes('[OFFEN: Anbieter Alarm-Postfach]')), offen.log.join(' | '))
const zweiMal = backend({ zusatz: '<p>[OFFEN: Aufbewahrung der Server-Sicherungen] und noch einmal [OFFEN: Aufbewahrung der Server-Sicherungen]</p>' })
assert.equal(await m.main(['--freigabe=2026-10-20'], zweiMal.deps), 1)
assert.equal(zweiMal.log.filter((z) => z.includes('offene Markierung'))[0].match(/Aufbewahrung der Server-Sicherungen/g).length, 1, 'jede Markierung nur einmal genannt')
for (const b of [offen, zweiMal]) assert.equal(schreibend(b).length, 0)
const kommentar = backend({ zusatz: '<!-- [OFFEN: nur eine Notiz im Quelltext] -->' })
assert.equal(await m.main(['--freigabe=2026-10-20'], kommentar.deps), 0, 'eine Markierung im HTML-Kommentar ist nicht sichtbar')
})
test('Prüfsumme neben dem PDF muss zur HTML passen, sonst Abbruch vor jedem Schreiben (veraltetes PDF)', async () => {
const veraltet = backend({ summe: 'a'.repeat(64) })
assert.equal(await m.main(['--freigabe=2026-10-20'], veraltet.deps), 1)
assert.ok(veraltet.log.some((z) => z.includes('das PDF ist nicht aus dieser HTML gedruckt') && z.includes('build-legal-pdfs.sh datenschutz')), veraltet.log.join(' | '))
const fehlt = backend({ summe: 'fehlt' })
assert.equal(await m.main(['--freigabe=2026-10-20'], fehlt.deps), 1)
assert.ok(fehlt.log.some((z) => z.includes('WEBklar-Datenschutzerklaerung.pdf.sha256') && z.includes('fehlt')), fehlt.log.join(' | '))
for (const b of [veraltet, fehlt]) assert.equal(schreibend(b).length, 0)
// passende Prüfsumme (Standard im Nachbau): der Upload geht durch
const passt = backend()
assert.equal(await m.main(['--freigabe=2026-10-20'], passt.deps), 0)
assert.equal(schreibend(passt).length, 4)
})
test('--probe liest nur: Bucket, Karte und Versionen, meldet eine fehlende Karte als Stopp', async () => { test('--probe liest nur: Bucket, Karte und Versionen, meldet eine fehlende Karte als Stopp', async () => {
const b = backend() const b = backend()
assert.equal(await m.main(['--probe'], b.deps), 0) assert.equal(await m.main(['--probe'], b.deps), 0)
+39 -11
View File
@@ -745,28 +745,37 @@ test('legal_datenschutz_anfragen: Abschnitt 11 beschreibt nur den Anfrage-Weg (K
const s = between(strip(html), '11. Interessenten: Antworten auf öffentliche Anfragen', '12. Empfänger im Überblick') const s = between(strip(html), '11. Interessenten: Antworten auf öffentliche Anfragen', '12. Empfänger im Überblick')
for (const t of ['Art. 14 DSGVO', 'KI-Assistent', 'Claude von Anthropic', 'von Hand', 'Automatische E-Mails verschicken wir nicht', 'Für E-Mails gilt Abschnitt 8', for (const t of ['Art. 14 DSGVO', 'KI-Assistent', 'Claude von Anthropic', 'von Hand', 'Automatische E-Mails verschicken wir nicht', 'Für E-Mails gilt Abschnitt 8',
// Quellen: Alarm-E-Mails und Websuche; Plattformen mit Bot-Verbot ausgenommen // Quellen: Alarm-E-Mails und Websuche; Plattformen mit Bot-Verbot ausgenommen
'Benachrichtigungs-E-Mails', 'Kleinanzeigen, freelancermap und freelance.de', 'Websuche', 'Frage-Foren', 'Die Suche und den Abruf der Seiten führt Anthropic aus', 'Facebook, Instagram, LinkedIn, Xing, Reddit', 'Benachrichtigungs-E-Mails', 'Kleinanzeigen, freelancermap und freelance.de', 'Websuche', 'Frage-Foren', 'Die Suche und den Abruf der Seiten führt Anthropic aus',
// feste Ausschlussliste (so wie BOT_VERBOT_DOMAINS im Dienst) statt eines Kriteriums
'Von der Websuche und vom Abruf ausgenommen sind die Plattformen Kleinanzeigen, freelancermap, freelance.de, Facebook, Instagram, LinkedIn, Xing, Reddit, Upwork, Fiverr und MyHammer sowie Seiten, deren robots.txt den Abruf sperrt',
'nicht nach bestimmten Personen', 'nicht nach bestimmten Personen',
// Daten // Daten
'Titel des Beitrags', 'Beitragstext (höchstens 4000 Zeichen)', 'den Link', 'Ort und Postleitzahl', 'die Plattform', 'Punktzahl mit kurzer Begründung', 'E-Mail-Adresse aus dem Beitrag', 'Titel des Beitrags', 'Beitragstext (höchstens 4000 Zeichen)', 'den Link', 'Ort und Postleitzahl', 'die Plattform', 'Punktzahl mit kurzer Begründung', 'E-Mail-Adresse aus dem Beitrag',
'Ein eigenes Feld für Ihren Namen gibt es nicht', 'Telefonnummern entfernen wir beim Speichern automatisch', 'Faustregel', 'Ein eigenes Feld für Ihren Namen gibt es nicht', 'Telefonnummern entfernen wir beim Speichern automatisch', 'Faustregel',
// Rechtsgrundlage, Empfänger // Rechtsgrundlage, Empfänger
'Art. 6 Abs. 1 lit. f DSGVO', 'Hetzner Online GmbH', 'Anthropic Ireland, Limited', 'als Auftragsverarbeiter', 'USA', 'Standardvertragsklauseln (Art. 46 Abs. 2 lit. c DSGVO)', 'Art. 6 Abs. 1 lit. f DSGVO', 'Hetzner Online GmbH', 'Anthropic Ireland, Limited', 'als Auftragsverarbeiter', 'USA', 'Standardvertragsklauseln (Art. 46 Abs. 2 lit. c DSGVO); eine Kopie erhalten Sie bei uns (support@webklar.com)',
// keine automatisierte Einzelentscheidung // keine automatisierte Einzelentscheidung: die KI sortiert aus, ein Mitarbeiter entscheidet über jede Antwort
'entscheidet ein Mitarbeiter (Abschnitt 14)', 'Die KI sortiert Anfragen aus, die nicht zu uns passen; auf diese schreiben wir nicht', 'Ob und was wir einer passenden Anfrage antworten, entscheidet ein Mitarbeiter (Abschnitt 14)',
// Fristen und Reste // Hinweis in der ersten Nachricht (der Dienst setzt ihn in Claras Entwurf; Wortlaut in text-weg-b-aenderungen.md)
'spätestens 90 Tage nach dem Fund', 'innerhalb von 30 Tagen', 'sperren und kürzen wir sie sofort', 'pseudonymisierte Prüfwerte (Hash-Werte)', 'Ort, Plattform, Punktzahl, Status', 'In unserer ersten Nachricht steht, dass eine KI-Assistentin von WEBklar antwortet', 'in unserer Datenbank speichern, dass Sie widersprechen können',
// Fristen und Reste: aussortierte Anfragen 29/30 Tage (Regel B), Absage und Widerspruch erst ab dem Lesen unserer Seite
'spätestens 90 Tage nach dem Fund', 'Passt Ihre Anfrage nach der Bewertung der KI nicht zu uns, schreiben wir Ihnen nicht und kürzen die Daten innerhalb von 30 Tagen nach dem Fund',
'innerhalb von 30 Tagen, nachdem wir Ihre Antwort gelesen haben', 'sperren und kürzen wir sie sofort, sobald wir Ihren Widerspruch lesen', 'pseudonymisierte Prüfwerte (Hash-Werte)', 'Ort, Plattform, Punktzahl, Status',
'jedoch ohne Titel, Text, Link und Kontaktdaten', 'bereinigen die zugehörigen Einträge im Protokoll', 'Abschnitt 5 und, wenn ein Vertrag zustande kommt, die Abschnitte 7 und 9', 'jedoch ohne Titel, Text, Link und Kontaktdaten', 'bereinigen die zugehörigen Einträge im Protokoll', 'Abschnitt 5 und, wenn ein Vertrag zustande kommt, die Abschnitte 7 und 9',
// Dauer der Reste (Art. 14 Abs. 2 lit. a: Kriterium)
'so lange auf, wie wir verhindern müssen, dass wir Sie wegen desselben Beitrags oder derselben E-Mail-Adresse erneut ansprechen',
// Kopien // Kopien
'spätestens nach 90 Tagen', 'Datensicherungen unseres Servers', 'Geschäftsbrief', 'spätestens nach 90 Tagen', 'Datensicherungen unseres Servers', 'Geschäftsbrief',
// Widerspruch // Widerspruch
'Ihr Widerspruchsrecht gegen diese Verarbeitung (Art. 21 DSGVO)', 'Antworten Sie dazu auf unsere Nachricht oder schreiben Sie an support@webklar.com']) assert.ok(s.includes(t), t) 'Ihr Widerspruchsrecht gegen diese Verarbeitung (Art. 21 DSGVO)', 'Antworten Sie dazu auf unsere Nachricht oder schreiben Sie an support@webklar.com',
'Sobald wir Ihre Nachricht lesen, sperren wir den Beitrag und, soweit wir sie gespeichert haben, Ihre E-Mail-Adresse',
'Wir erkennen Sie dabei nur am Beitrag und an der E-Mail-Adresse', 'Ein neues Gesuch, das Sie später einstellen, ist für uns ein neuer Beitrag; sagen Sie uns dann bitte noch einmal Bescheid']) assert.ok(s.includes(t), t)
// der Widerspruch steht im Kasten (box widerspruch) im Abschnitt 11 // der Widerspruch steht im Kasten (box widerspruch) im Abschnitt 11
const kasten = html.slice(html.indexOf('<a id="interessenten">'), html.indexOf('<h2>12. Empfänger im Überblick</h2>')) const kasten = html.slice(html.indexOf('<a id="interessenten">'), html.indexOf('<h2>12. Empfänger im Überblick</h2>'))
assert.ok(kasten.includes('<div class="box widerspruch">')) assert.ok(kasten.includes('<div class="box widerspruch">'))
}) })
test('legal_datenschutz_anfragen: nichts aus dem Kaltmail-Weg (Stufe 2) und keine Zusage, die der Dienst heute nicht einhält', () => { test('legal_datenschutz_anfragen: nichts aus dem Kaltmail-Weg (Stufe 2); offene Tatsachen stehen als sichtbare [OFFEN]-Markierung im Text', () => {
const html = read(DS) const html = read(DS)
const ds = strip(html) const ds = strip(html)
const s = between(ds, '11. Interessenten: Antworten auf öffentliche Anfragen', '12. Empfänger im Überblick') const s = between(ds, '11. Interessenten: Antworten auf öffentliche Anfragen', '12. Empfänger im Überblick')
@@ -775,8 +784,25 @@ test('legal_datenschutz_anfragen: nichts aus dem Kaltmail-Weg (Stufe 2) und kein
'Postfach bei der Hetzner', 'In allen Fällen bleiben nur']) assert.ok(!s.includes(t), `noch da (Stufe 2 oder zu viel versprochen): ${t}`) 'Postfach bei der Hetzner', 'In allen Fällen bleiben nur']) assert.ok(!s.includes(t), `noch da (Stufe 2 oder zu viel versprochen): ${t}`)
// Resend, Cloudflare und Overpass stehen nirgends in der Erklärung // Resend, Cloudflare und Overpass stehen nirgends in der Erklärung
for (const t of ['Resend', 'Cloudflare', 'Overpass']) assert.ok(!ds.includes(t), `${t} gehört nicht in die Weg-B-Fassung`) for (const t of ['Resend', 'Cloudflare', 'Overpass']) assert.ok(!ds.includes(t), `${t} gehört nicht in die Weg-B-Fassung`)
// keine sichtbaren Platzhalter für offene Punkte (die stehen in der Notiz text-offen.md, nicht im Text) // Offene Tatsachen (Faktenblatt) stehen als sichtbare Markierung im Text, solange sie ungeklärt sind; der Upload (scripts/datenschutz-hochladen.mjs)
assert.ok(!/\[OFFEN|\[Prüfen|TODO|XXX|\{[a-z]+\}/i.test(strip(html.replace(/<!--[\s\S]*?-->/g, ''))), 'keine offenen Markierungen im sichtbaren Text') // bricht ab, solange eine Markierung steht. Erlaubt sind nur diese beiden; jede steht im Span class="offen" (gelb hinterlegt). Sind sie geklärt, fallen sie weg.
const ERLAUBT = new Set(['[OFFEN: Anbieter Alarm-Postfach]', '[OFFEN: Aufbewahrung der Server-Sicherungen]'])
const sichtbar = html.replace(/<!--[\s\S]*?-->/g, '')
const marken = [...sichtbar.matchAll(/\[OFFEN[^\]]*\]/g)].map((m) => m[0])
for (const m of marken) assert.ok(ERLAUBT.has(m), `unerlaubte offene Markierung: ${m}`)
assert.equal(marken.length, (sichtbar.match(/<span class="offen">\[OFFEN: [^\]<]*\]<\/span>/g) ?? []).length, 'jede Markierung steht im Span class="offen"')
assert.ok([0, 2].includes(marken.filter((m) => m === '[OFFEN: Anbieter Alarm-Postfach]').length), 'Alarm-Postfach: im Absatz über Kopien und in Abschnitt 12 (oder in beiden geklärt)')
// keine anderen Platzhalter
assert.ok(!/\[Prüfen|TODO|XXX|\{[a-z]+\}/i.test(strip(sichtbar.replace(/\[OFFEN[^\]]*\]/g, ''))), 'keine anderen Platzhalter im sichtbaren Text')
})
test('legal_datenschutz_anfragen: Zusagen nur in der Reichweite des Dienstes (Sperre nur je Beitrag und E-Mail-Adresse, Fristen ab dem Lesen), keine zu weiten Aussagen', () => {
const ds = strip(read(DS))
const s = between(ds, '11. Interessenten: Antworten auf öffentliche Anfragen', '12. Empfänger im Überblick')
// der Dienst sperrt nur denselben Beitrag oder dieselbe Adresse und erreicht keine Antwort auf einer Plattform: keine Zusage "nicht mehr schreiben" ohne Einschränkung
for (const t of ['schreiben Ihnen nicht mehr', 'melden uns nicht wieder', 'sperren Ihre Daten dann sofort', 'derzeit unter anderem Facebook', 'Plattformen, die automatisierte Zugriffe untersagen', 'entscheidet nicht allein', 'kurze Zeit enthalten'])
assert.ok(!ds.includes(t), `zu weit: ${t}`)
assert.ok(s.includes('schreiben Ihnen dazu nicht mehr'), 'die Sperre gilt "dazu", also für den Beitrag und die Adresse')
}) })
test('legal_datenschutz_anfragen: Abschnitt 2, 12 und 14 passen zu Abschnitt 11 und es gibt keinen Widerspruch zu 8', () => { test('legal_datenschutz_anfragen: Abschnitt 2, 12 und 14 passen zu Abschnitt 11 und es gibt keinen Widerspruch zu 8', () => {
@@ -786,10 +812,12 @@ test('legal_datenschutz_anfragen: Abschnitt 2, 12 und 14 passen zu Abschnitt 11
const emp = between(ds, '12. Empfänger im Überblick', '13. Ihre Rechte') const emp = between(ds, '12. Empfänger im Überblick', '13. Ihre Rechte')
assert.ok(emp.includes('Anthropic Ireland, Limited: Bewertung öffentlicher Anfragen, Entwurf von Antworten sowie Websuche und Seitenabruf durch ein Sprachmodell (Auftragsverarbeiter, Daten auch in den USA, Abschnitt 11)')) assert.ok(emp.includes('Anthropic Ireland, Limited: Bewertung öffentlicher Anfragen, Entwurf von Antworten sowie Websuche und Seitenabruf durch ein Sprachmodell (Auftragsverarbeiter, Daten auch in den USA, Abschnitt 11)'))
assert.ok(emp.includes('Plattformen, auf denen wir Ihre Anfrage beantworten')) assert.ok(emp.includes('Plattformen, auf denen wir Ihre Anfrage beantworten'))
// der Anbieter des Alarm-Postfachs steht in der Liste, solange er offen ist als Markierung (danach als Name)
assert.ok(emp.includes('E-Mail-Postfach für die Benachrichtigungen der Plattformen (Abschnitt 11)'))
assert.ok(!emp.includes('Betrieben') && !emp.includes('Website-Texte'), 'der Anthropic-Zweck nennt keine Website-Texte von Betrieben mehr') assert.ok(!emp.includes('Betrieben') && !emp.includes('Website-Texte'), 'der Anthropic-Zweck nennt keine Website-Texte von Betrieben mehr')
const s14 = between(ds, '14. Pflicht zur Bereitstellung', '15. Änderungen') const s14 = between(ds, '14. Pflicht zur Bereitstellung', '15. Änderungen')
assert.ok(s14.includes('keine automatisierten Einzelentscheidungen nach Art. 22 DSGVO')) assert.ok(s14.includes('keine automatisierten Einzelentscheidungen nach Art. 22 DSGVO'))
assert.ok(s14.includes('Die Bewertung öffentlicher Anfragen durch einen KI-Assistenten (Abschnitt 11) bereitet nur eine Antwort vor; ob und was wir antworten, entscheidet ein Mitarbeiter, der die Antwort von Hand abschickt.')) assert.ok(s14.includes('Die Bewertung öffentlicher Anfragen durch einen KI-Assistenten (Abschnitt 11) sortiert Anfragen aus, die nicht zu uns passen; auf diese schreiben wir nicht. Für alle anderen bereitet sie nur eine Antwort vor; ob und was wir antworten, entscheidet ein Mitarbeiter, der die Antwort von Hand abschickt.'))
assert.ok(!ds.includes('keine automatisierten Entscheidungen'), 'die zu weite alte Überschrift ist weg') assert.ok(!ds.includes('keine automatisierten Entscheidungen'), 'die zu weite alte Überschrift ist weg')
// Einleitung nennt die Antwort auf öffentliche Anfragen // Einleitung nennt die Antwort auf öffentliche Anfragen
assert.ok(ds.includes('oder wenn wir auf Ihre öffentliche Anfrage nach einer Website antworten')) assert.ok(ds.includes('oder wenn wir auf Ihre öffentliche Anfrage nach einer Website antworten'))