12 — legal-compliance design
12 — legal-compliance design
Section titled “12 — legal-compliance design”Status: Draft
Date: 2026-05-23
Owner: legal + frontend
Parent: 2026-05-23-nowyourlink-master-design.md (binding — D7, D10, D29, D30, §4.1, §6)
Source: OutBidThis Gesamtkonzept.pdf §9 (pp. 18–19)
1. Purpose & scope
Section titled “1. Purpose & scope”Technical implementation guidance for legal/regulatory compliance — NOT legal opinion. Every template/clause MUST be reviewed by a qualified German lawyer (Fachanwalt IT-/Steuerrecht) before launch.
In: legal templates (Impressum, AGB, Datenschutz, Cookie Notice, Risk Notice); §14 UStG invoice fields; DSGVO map (Art. 13/15/17/20/30); retention; ELSTER + USt-IdNr. ops; B2B-only enforcement; ban-list under Art. 6(1)(f). (Disqus cookie consent was originally in scope but is removed — Disqus was dropped from the product, see §6.)
Out: legal advice; tax filings; content policy (03); identity mechanics (02); saga producing invoices (05).
2. Document templates
Section titled “2. Document templates”Sources live in packages/legal/templates/ as Markdown with {{ company.* }} placeholders. Build-time packages/legal/render.ts interpolates from packages/legal/config.ts and emits .html (for 07) and .md (for 11). Locales: en/ + de/; German is binding per AGB §1.
| File | Anchor | Required fields |
|---|---|---|
impressum.md | §5 DDG, §18 Abs. 2 MStV | Firmenname, Rechtsform, Anschrift, Vertretungsberechtigter, Tel/Email, HRB-Nr., USt-IdNr. (§27a UStG), Verantwortlicher i.S.d. §18 MStV, EU-OS-Link |
agb.md | §§305 ff. BGB | Geltungsbereich (B2B-only: “Unternehmer i.S.d. §14 BGB”), Vertragsgegenstand, Auktionsregeln (sealed-bid, 1 € min, 1 € increment, 00:00 UTC close, 24 h aktivierung), Zuschlag, Zahlung (Stripe, sofort fällig), Haftung (begrenzt auf Auftragswert), Strike-Regelung, Gerichtsstand (Anbietersitz), deutsches Recht, UN-Kaufrecht ausgeschlossen, expliziter Ausschluss §312g BGB Widerrufsrecht |
datenschutz.md | Art. 13 DSGVO, §25 TTDSG | Verantwortlicher, Zwecke + Rechtsgrundlagen (Art. 6(1)(b) Vertrag, (c) §147 AO, (f) Betrugsabwehr/Ban-List), Empfänger (Stripe, Cloudflare, Resend, Sentry), Drittland + SCCs, Speicherdauern (§4), Betroffenenrechte (Art. 15–21), Beschwerderecht |
cookie-notice.md | §25 TTDSG | Nur technisch notwendige First-Party-Cookies; keine nicht-erforderlichen Cookies (Disqus entfernt, §6) → keine Einwilligung nötig |
risk-notice-advertiser.md | informational | UWG-Hinweis, §6 TMG Kennzeichnungspflicht, Markenrechtsverletzungs-Haftungsverlagerung |
packages/legal/config.ts keys: company.{name, legal_form, address, represented_by, contact_email, phone, register_court, register_no, ust_id}. CI gate (in 01) rejects empty values.
3. Invoice template (§14 UStG)
Section titled “3. Invoice template (§14 UStG)”Stripe Invoicing produces the PDF per D10 + master §10.1; saga 05 populates fields.
§14 Abs. 4 UStG mandatory fields: (1) Name+Anschrift Leistender — Stripe account; (2) Name+Anschrift Leistungsempfänger — D1 user; (3) Steuernummer/USt-IdNr. Leistender — Stripe; (4) Ausstellungsdatum — Stripe created; (5) Fortlaufende, einmalige Rechnungsnummer — Stripe invoice.number (configure sequential, no gaps); (6) Leistungsbezeichnung — line item "Spotlight — {auction_id}"; (7) Leistungszeitpunkt — custom_field Leistungsdatum = auction_id; (8) Entgelt nach Steuersätzen — Stripe automatic_tax; (9) Steuersatz + Steuerbetrag, oder Befreiungshinweis — see §8; (10) §14b UStG Hinweis — Stripe footer: “Diese Rechnung ist 10 Jahre aufzubewahren (§147 AO).”
Conditional clauses (saga injects via custom_fields from D1 user country + VIES status):
- DE → EU-B2B (VIES
valid): “Steuerschuldnerschaft des Leistungsempfängers gem. §13b UStG / Art. 196 MwStSystRL. Reverse charge.” Both parties’ USt-IdNr. printed. - DE → Drittland: “Nicht im Inland steuerbare Leistung gem. §3a Abs. 2 UStG.”
- DE → DE: 19 % USt regulär.
Credit notes use creditNotes.create (saga 05 §9) with identical mandatory-field set.
4. Retention policy matrix
Section titled “4. Retention policy matrix”Enforced by per-store TTL + daily q-retention worker (packages/legal/retention/).
| Datum | Frist | Rechtsgrundlage | Erzwingung |
|---|---|---|---|
| Rechnungen (PDF) | 10 Jahre | §147 AO, §14b UStG | D29 dual-write: R2 nyl-invoices-prod + hosted_invoice_url pinned in D1 invoices. R2 Object Lock (Compliance, 10y) once GA; pre-check head() write-once guard while Beta |
Saga audit (saga_runs) | 10 Jahre | §147 AO (Geschäftsbrief) | no auto-delete |
| Verlierer-Bid-Daten | max. 6 Monate | PDF §9.5; Art. 5(1)(c) | nightly redact ad_id, keep anonymised aggregates |
| User-Daten bei Löschantrag | sofort anonymisiert | Art. 17 DSGVO | anonymizeUser() in 02 |
Ban-List (hashed_vat_id, hashed_email, strikes, banned_at) | unbefristet (Admin kann streichen) | Art. 6(1)(f) Betrugsabwehr | own table; survives anonymizeUser (§5) |
| Conversion-Pixel-Events | 13 Monate | data minimisation; GA4-Mindestmaß | q-retention daily |
| Push-Subscriptions | bis Widerruf | Art. 6(1)(a) | unsubscribe löscht sofort |
| — | — | Entfernt (2026-06-13) — Disqus aus dem Produkt entfernt (§6); kein Archiv mehr. | |
| Magic-link Token | 15 min | technisch erforderlich | KV TTL |
| Sessions | 30 d sliding / 90 d cap | Art. 6(1)(b) | KV TTL |
| VAT-validation attempts | 4 Jahre | §147 AO (Buchungsbeleg) | no auto-delete |
| Cookie-Consent-Log | 3 Jahre | Nachweispflicht Art. 7(1) | KV expirationTtl |
| Sentry-Logs (PII-frei) | 90 d | Diagnose | Sentry-Policy |
D29 dual-write detail: in INVOICE_FINALIZE (saga 05 §3), after R2 PUT, saga also writes invoices(invoice_id, hosted_invoice_url, customer_id, auction_id, amount_cents, finalized_at, r2_key, hash_sha256) to D1. If R2 ever loses the object, hosted_invoice_url is canonical (Stripe retains ≥ 10 y).
5. DSGVO request workflow
Section titled “5. DSGVO request workflow”/api/me/* routes are mounted on apps/api as Hono handlers (ADR-0001 / D40); the 08 dashboard at apps/web links to them. Session auth via the shared middleware; routes forward to 02.anonymizeUser() (etc.) via the USER_READ service binding.
| Art. | Endpoint | UI | Backend |
|---|---|---|---|
| 15 / 20 | GET /api/me/data-export | 08 | JSON-zip: users + bids + invoice links + saga + pixel; emailed |
| 17 | POST /api/me/delete-account | 08 | 02.anonymizeUser(); ban-list preserved |
| 21 | POST /api/me/object | 08 | admin reviews legitimate-interest objection |
| 16 | PATCH /api/me/profile | 08 | VAT-ID change triggers VIES re-validation (02) |
| 30 | internal snapshot | 10 | nightly vvt.md snapshot |
SLA: 30 Tage (Art. 12(3)). Diagram in processes/12-invoice-retention-and-dsgvo-erasure.md.
Art. 17 vs ban-list — conflict resolution
Section titled “Art. 17 vs ban-list — conflict resolution”- If unpaid invoices → block with
legal.unpaid-invoices(Art. 17(3)(b)). 02.anonymizeUser(): overwriteemail,company_name,vat_id,countrywith deterministic hashes; null Stripe ids; preservecreated_at; clear sessions + magic-links.- Insert
ban_list(hashed_vat_id, hashed_email, strikes, banned_at, last_strike_at, reason_codes[], retained_under='Art.6(1)(f)')— survives anonymisation. Legal basis: berechtigtes Interesse Betrugsabwehr; documented indatenschutz.md. - Confirmation mail with Art. 14 transparency notice if ban-list row preserved.
- Audit row in
user_admin_actions(immutable; owned by02).
Future registration with same VAT-ID hash → 02 /register returns legal.banned-entity. Admin override in 10.
6. Cookie consent — optional GA4 (2026-09-13)
Section titled “6. Cookie consent — optional GA4 (2026-09-13)”The owner explicitly requested GA4 with consent and conversion funnels on 2026-09-13. This updates the earlier no-banner assumption; Disqus remains removed. Existing Cloudflare analytics remains cookieless. GA4 is optional and must remain inactive until a valid production measurement ID is configured and the operator has approved Google processing terms, transfer arrangements, and the privacy disclosure. This implementation request does not attest that those prerequisites are complete.
Use basic consent mode: no Google script or request before explicit analytics opt-in. Reject and withdraw must be as accessible as acceptance; keep advertising purposes denied. Store the analytics choice for 180 days and cap GA cookies at 180 days. Withdrawal stops collection and removes GA cookies. Do not reinterpret the retired Disqus functional consent as analytics consent. Honour DNT and Global Privacy Control.
Only sanitized page paths and allowlisted funnel events may be sent. Exclude query strings, fragments, identifiers, bid amounts, creative content, and payment information. Do not label a magic-link request as successful login or registration. GA4 property retention, enhanced measurement settings, key events, and delivery verification remain separate activation checks; this document does not claim they are configured.
The retired Disqus consent log is not a GA4 consent-proof implementation. The owner must approve the consent evidence and retention approach before activation. No legal approval is asserted by this specification update.
7. B2B-only enforcement
Section titled “7. B2B-only enforcement”Three layers:
- Registration (
02 /register) —company_name+countryrequired;vat_idrequired for EU; non-EU → admin queue (D7). Form copy: “Nur Unternehmer i.S.d. §14 BGB. Verbraucher können sich nicht registrieren.” - AGB §1 — Vertrag nur mit Unternehmern; Widerrufsrecht §312g BGB ausgeschlossen; checkbox at registration writes
users.b2b_acknowledged_at. - Invoice — Stripe Customer
tax_exempt: 'none' | 'reverse', never'exempt'; never reference “Verbraucher”.
Widerrufsrecht does not apply. Self-serve deletion uses Art. 17 DSGVO (§5).
8. USt handling matrix
Section titled “8. USt handling matrix”automatic_tax: { enabled: true } on invoices.create (05 §4).
| Konstellation | Stripe | Invoice text |
|---|---|---|
| DE → DE | 19 % USt | regulär |
DE → EU-B2B (VIES valid) | 0 %, reverse_charge | ”§13b UStG / Art. 196 MwStSystRL — reverse charge” |
DE → EU-B2B (VIES ungültig / vies_degraded_pending) | 19 % DE-USt (Stripe-Fallback) | regulär — bidder im Dashboard gewarnt (08) |
| DE → Drittland | 0 %, nicht steuerbar | ”§3a Abs. 2 UStG nicht im Inland steuerbar” |
Fallback for Stripe Tax error: if invoices.create returns automatic_tax.status='failed', saga raises NonRetryableError; admin manually finalises with correct tax. Stripe Tax at invoice time is the legal source of truth; VIES at registration is advisory (02 §4).
9. ELSTER + USt-IdNr. — ops note
Section titled “9. ELSTER + USt-IdNr. — ops note”Founder pre-launch (not code): Gewerbeanmeldung / GmbH-HRB → Steuernummer → USt-IdNr. beim BZSt (Pflicht für Reverse-Charge) → ELSTER-Zertifikat → monatliche USt-Voranmeldung (§18 Abs. 2 UStG, Neugründer-Pflicht) → ZM monatlich bei EU-Reverse-Charge. Bookkeeping: Lexware Office oder sevdesk mit Stripe-Connector + DATEV-Export. Published under docs-site operations (11).
10. Pre-launch compliance checklist (PDF §9.7)
Section titled “10. Pre-launch compliance checklist (PDF §9.7)”| # | Item | Owner | Spec |
|---|---|---|---|
| 1 | Impressum, AGB, Datenschutz, Cookie Notice, Risk Notice — lawyer-reviewed | founder | 12 + 07 |
| 2 | §14 UStG fields verified on real test invoice (DE, EU-RC, non-EU) | founder | 12 + 05 |
| 3 | USt-IdNr. issued; ELSTER live; ZM dry-run | founder | 12 |
| 4 | Art. 30 Verarbeitungsverzeichnis (vvt.md) populated | founder | 12 |
| 5 | DSGVO endpoints (Art. 15/17/20) live + tested | backend | 08 + 10 + 12 |
| 6 | frontend | 07 + 12 | |
| 7 | B2B-Checkbox + AGB §1 at registration | frontend | 02 + 12 |
| 8 | Ban-list survives DSGVO erasure (integration test) | backend | 02 + 12 |
| 9 | R2 invoice retention dual-write verified | backend | 05 + 12 |
| 10 | Art. 28 AVV signed with all sub-processors (Stripe, CF, Resend, Sentry, Lexware/sevdesk) | founder | 12 |
All 10 green in CI legal-gate before public deploy.
11. Sub-system touchpoints
Section titled “11. Sub-system touchpoints”| Spec | Touchpoint |
|---|---|
02-identity-onboarding | B2B form copy, D30: VIES-degraded cap = 100 € (not 200 €) — confirmed here, 02 §4 must update before launch; ban-list write on anonymizeUser; DOI text |
03-ad-moderation | content-compliance (HWG, UWG, Markenrecht); risk-notice ack at upload |
05-payment-saga | invoice custom_fields, reverse-charge clauses, R2+D1 dual-write, credit-note PDFs |
07-public-site-archive | renders packages/legal/ templates; footer legal links (no cookie-consent gate — Disqus removed, §6) |
08-advertiser-dashboard | DSGVO self-serve endpoints; conversion-pixel privacy text; ban-status display |
10-admin-console | DSGVO handling UI; ban-list management; manual VIES override; retention-job monitor |
11-docs-site | publishes ELSTER/ZM ops notes under /operations/legal/ |
12. Open items requiring legal counsel
Section titled “12. Open items requiring legal counsel”- §34b GewO Versteigerer-Erlaubnis — required for digital ad-slot auction? PDF §9.2.
- §18 Abs. 2 MStV Verantwortlicher — required given UGC (advertiser creatives)? (Disqus comments removed — §6.)
- HWG + UWG auto-reject categories in
03(Rx, Glücksspiel, Finanzversprechen). - Markenrecht-Haftungsverlagerung AGB clause — §307 BGB AGB-Kontrolle.
- §25 TTDSG vs CF Web Analytics — cookieless but reads
referer; consent needed? - Stripe Tax wrong rate — liability vs §14 Abs. 4 Nr. 8 UStG.
- AVV completeness for CF sub-processors (R2, D1, Workers, DOs).
- Strike ban duration — indefinite vs Verhältnismäßigkeit; sunset (5 y)?
- Zuschlag-Moment — Vertragsschluss bei
auction.closedoderinvoice.payment_succeeded? - SCC + TIA depth für Stripe US / Sentry US.
13. Acceptance criteria
Section titled “13. Acceptance criteria”- All five legal documents render at
/legal/*in EN + DE; German binding. - Finalized invoice for each tax constellation (DE, EU-RC, non-EU) carries all 10 §14 UStG fields + correct conditional clause; snapshot-tested.
- Deletion of user with
strikes ≥ 1anonymisesusers, preservesban_list, blocks re-registration with same VAT-ID hash. Disqus iframe does not load on first visit; loads only after consent; consent_id in KV with 3 y TTL.Retired (2026-06-13) — Disqus removed (§6); no non-essential cookies, so no consent gate or log.q-retentiondry-run purges loser bids > 6 m, pixel events > 13 m. (Disqus archive purge retired — Disqus removed, §6.)- D29 dual-write: every finalized invoice has R2 object AND
hosted_invoice_urlin D1 within 2 min. - Art. 15 export < 30 s, includes all §5 categories.
- Pre-launch §10 checklist all 10 green in CI legal-gate.
14. Agent-team hints
Section titled “14. Agent-team hints”- Layout:
packages/legal/{templates,config.ts,render.ts,retention/,__tests__}. - CI gate
pnpm legal:check:config.tsnon-empty; each template references its required fields; German version matches hash inAPPROVED_HASHES.json; no unresolvedTODO(lawyer): …markers inmain. - No inline legal text in Astro. Use
<LegalDoc slug="agb" locale="de" />island importingpackages/legal. - No final legal copy without lawyer sign-off.
- Retention worker: cron
0 3 * * *UTC; D1+KV+R2 bindings; idempotent; emitslegal.retention.executedto Sentry. - Sentry tags:
subsystem=legal-compliance,retention_job,dsgvo_request_type. - No PII at debug level. Use
hashUser()from02for breadcrumbs. - This spec is implementation guidance only. Final legal review by qualified German counsel is required before public launch.