Per-tenant extracts available on request via post@preferium.no. There is no self-service export in the dashboard.
GDPR Article 30 — Records of Processing Activities
Document status: Template for Preferium AS as Processor on behalf of Customers (Controllers). Required by Regulation (EU) 2016/679 Article 30(2). Last updated 2026-09-20.
Owner: Robert Andre Johansen (operational Privacy Contact; no appointed DPO — see dpo-designation.md).
Review cadence: Quarterly + on any change to processing scope, sub-processors, retention, or transfer mechanism.
A. Controller / Processor identity
| Field | Value |
|---|---|
| Name | Preferium AS |
| Org. nr. | 999 323 286 |
| Registered address | Sponheimveien 19, 1613 Fredrikstad, Norway |
| Office address | Produksjonsveien 18, 2nd floor, 1618 Fredrikstad, Norway |
| Public registers | Enhetsregisteret (entered 5 January 2013), Foretaksregisteret, Merverdiavgiftsregisteret |
| Establishment within EU/EEA | Norway (member of EEA, fully subject to GDPR via EEA-EFTA acquis) |
| Data Protection Officer | Not appointed; current assessment is recorded in dpo-designation.md |
| Privacy contact email | post@preferium.no |
| Lead Supervisory Authority | Datatilsynet (Norwegian Data Protection Authority — datatilsynet.no) |
| Representative inside the EU | Not required — Preferium AS is established in EEA |
B. Processing activities (one row per distinct purpose)
| # | Purpose of processing | Categories of data subjects | Categories of personal data | Retention | Lawful basis (Art. 6) | Source |
|---|---|---|---|---|---|---|
| 1 | Serve optimized HTML to AI crawlers on behalf of the Controller | Visitors (AI agents + humans) to Customer’s site | IP address (truncated to /24), User-Agent, request path, optimized HTML payload | 7 days (CF edge logs) / 30 days (ai_crawler_visits) |
Art. 6(1)(f) Legitimate interest (operate the Service) | HTTP requests proxied through CF Worker |
| 2 | Crawl Customer’s site to populate pages table |
Page authors named in byline/author tags |
Names, social handles, biographies if present in <meta> / JSON-LD authored by Customer |
Until Customer deletes the page row OR account closure | Art. 6(1)(b) Contract (Customer instructs us to crawl) | Customer’s own published HTML |
| 3 | Generate AI optimizations + store in Postgres | Same as #2 | Same as #2 (we don’t add identifying data; we reword existing copy) | Until Customer deletes the page row OR account closure | Art. 6(1)(b) Contract | Cloudflare Workers AI via AI Gateway (default @cf/meta/llama-4-scout-17b-16e-instruct) |
| 4 | Track Customer brand visibility in 4 LLMs (Phase 13) | Customer’s brand mentions in LLM responses | LLM-generated text, citations, sentiment scores. No human data subjects | 2 years (rolling) | Art. 6(1)(b) Contract | OpenAI/Anthropic/Perplexity/Gemini APIs |
| 5 | Tenant sign-up, billing, subscription management | Account owners + invited members | Email, name, password (hashed via Supabase Auth), Stripe customer ID, plan tier, login timestamps | Account lifetime + 5 years (billing — Bokføringsloven) | Art. 6(1)(b) Contract | Dashboard sign-up / Supabase Auth |
| 6 | Operational logging (audit trail of every privileged action) | Tenant users (owner/admin/member) | User ID, action type, IP, User-Agent, request body summary | 24 months (most); 5 years for billing. / sso. / gdpr. actions — see §E “Right to erasure” |
Art. 6(1)(c) Legal obligation (Bokføringsloven §13 for billing) | API workers — audit_logs |
| 7 | Customer support email correspondence | Tenant users + their nominees | Email, name, content of message | 3 years from last interaction | Art. 6(1)(b) Contract + Art. 6(1)(f) for support administration | post@preferium.no |
| 8 | Outbound webhook delivery + retry log | Tenant users (subjects of audit events) | User ID embedded in webhook payload (mirrors audit_logs.user_id); webhook URL + response codes |
90 days | Art. 6(1)(b) Contract | webhook_deliveries |
| 9 | OAuth token storage (Google Search Console + Analytics) | Tenant user who connected the account | Encrypted (AES-GCM) refresh + access tokens, OAuth scopes, Google email | Until Customer revokes OR account closure | Art. 6(1)(a) Consent (explicit per Google OAuth flow) | oauth_tokens |
| 10 | DSAR (export/delete/rectify) request fulfillment | Data subjects exercising Art. 15-22 rights | Email of requester, request type, response artifact (export ZIP, deletion log) | 3 years from completion | Art. 6(1)(c) Legal obligation | audit_logs (gdpr.export/gdpr.erasure events) |
| 11 | Cost-tracking telemetry (cents-precision per API call) | None (no human data subjects) | Provider name, token counts, cost — no user IDs attached | 13 months rolling | Not personal data — no Art. 6 basis required | AI Gateway analytics + per-route middleware |
| 12 | Consent log (proves opt-in to telemetry / marketing) | Tenant users | User ID, consent type, given/revoked timestamp | 5 years from revocation | Art. 6(1)(c) Legal obligation (proof of consent) | consent_log |
Activities #1, #4, #11 typically don’t process personal data, but the records are kept anyway because lines blur (e.g., IP-derived patterns, brand mentions referencing named individuals).
C. Recipients (sub-processors)
Public register published at https://preferium.com/subprocessors (its only canonical copy, maintained in the preferium.com repository). Summary:
| Recipient | Service | Hosting region | Transfer mechanism |
|---|---|---|---|
| Cloudflare, Inc. | Workers compute, KV cache, R2 object storage, DNS, AI Gateway, Browser Rendering | US/EU (data-resident) | EU SCCs + UK IDTA + DPF (where US) |
| Cloudflare, Inc. (Workers AI) | Standard generative AI (SEO meta, headings, JSON-LD, alt-text, translations and citation-prompt suggestions) via AI Gateway | US/EU (data-resident) | EU SCCs + UK IDTA + DPF (where US) |
| Supabase, Inc. | Managed Postgres + Auth (project region eu-north-1) |
EU (Stockholm) | No transfer — EU intra-region |
| Anthropic PBC | Claude responses for brand-visibility measurement | US | EU SCCs (via Cloudflare AI Gateway DPA) |
| OpenAI, LLC | LLM citation tracking (ChatGPT) | US | EU SCCs (direct DPA, see vendor agreements) |
| Google LLC | OAuth + Search Console + Analytics + KG + PageSpeed Insights + Gemini visibility measurement | US | DPF + EU SCCs (Google Workspace DPA) |
| Perplexity AI, Inc. | LLM citation tracking | US | EU SCCs |
| Stripe Payments Europe Ltd. | Billing + payment processing | EU (Dublin) | No transfer — EU intra-region |
| Resend, Inc. | Transactional + marketing email | US (with EU pop) | EU SCCs |
| DataForSEO LLC | SEO data (keywords, backlinks, SERP) | EU (Vilnius) | No transfer — EU intra-region |
| Sentry, Inc. | Application monitoring | US/EU (data-resident) | EU SCCs (EU pop available; enabled per Sentry DPA) |
Standard generation and prompt suggestions use Cloudflare Workers AI.
packages/shared/src/ai-models.tslists only Workers AI optimization models.citation-prompt-suggest.tsuses the configured optimization model through the routing wrapper inlib/cost-tracking/anthropic.ts; that historical filename does not identify the provider actually called. Read-only production configuration on 2026-09-19 confirmed@cf/meta/llama-4-scout-17b-16e-instruct. Claude visibility measurement incitation-tracking/providers.tsexplicitly selects Anthropic; OpenAI, Google Gemini and Perplexity also provide measurement responses. These API observations are not proof of an identical response in a consumer assistant UI. Provider changes require corresponding updates to the model catalog, the canonical sub-processor register and Service Terms on preferium.com, and this record.
Oppbevaringstider er avledet, ikke uavhengige. Kilden er
packages/shared/src/retention.ts(RETENTION_POLICIES, konsumert avservices/retention/purge.ts), og de MÅ stemme med personvernerklæringen på preferium.com/privacy — begge publiseres offentlig (denne fila på trust.preferium.com viaapps/trust/src/pages/gdpr-article-30.astro, personvernerklæringen på preferium.com). Rad 1 sto på «14 dager / 90 dager» fram til 2026-08-02 mens koden gjorde 30 dager og personvernerklæringen lovet 30/7 — to live juridiske sider fra samme selskap med ulikt svar. Endrer du en oppbevaringstid: endreretention.tsFØRST, deretter begge dokumentene.
Sub-processor changes notified per the DPA §6 (preferium.com/dpa) — 30 days advance notice + objection-right within 14 days.
D. Transfers to third countries
| Country | Mechanism | Adequacy decision? |
|---|---|---|
| USA | DPF + EU SCCs (Module 2 + Module 3) | Yes — EU-US DPF (2023) |
| UK | UK IDTA + UK addendum to EU SCCs | Yes — UK adequacy (2021) |
International transfers ALL covered by either an EU Commission adequacy decision OR EU/UK standard contractual clauses. Robert reviews adequacy status at each quarterly Art. 30 review.
E. Technical and organizational measures (Art. 32)
Summary — full implementation status in enterprise-readiness.md:
-
Encryption in transit: TLS 1.2 minimum, TLS 1.3 preferred and negotiated by all modern clients (Cloudflare edge floor; customer custom hostnames provisioned with
min_tls_version: '1.2'— seepackages/integrations/src/cloudflare/saas.ts). HSTS preload withincludeSubDomains(R168). -
Encryption at rest: Supabase Postgres AES-256 (Supabase platform default). OAuth tokens AES-GCM-encrypted in Worker memory (key never reaches Postgres — P7/P18).
-
Pseudonymization: Tenant data partitioned by tenant_id + RLS isolation. Edge IP truncated to /24 before storage (R157 audit-log spec).
-
Access control: Supabase Auth + RLS (multi-tenant
is_member_of()). Role-based: owner/admin/member/sub_user. saas_admin is gated separately by therequireMfamiddleware — seeMFA-PRIVILEGED-001. -
Multi-factor authentication (
MFA-PRIVILEGED-001): therequireMfamiddleware is implemented and unit-tested, and an unset or invalidMFA_ENFORCEMENT_MODEresolves to the fail-closedenforcedefault in production. No production configuration readback and no privileged-user enrolment has been verified, so this record does not represent MFA as in force. The single public status for this control is the trust-centre registry row. -
Single Sign-On (
SSO-SAML-001): the SAML 2.0 routes are implemented (metadata, login, start, callback, configs) with IdP metadata, InResponseTo binding and Enterprise-plan gating, backed bytenant_sso_configs. No customer identity provider has been configured end to end, so Enterprise SSO is not in service. This record previously described the routes as 503 stubs while the trust centre described them as live; neither matched the source, which is why both now defer to the registry row. -
Audit logging: Tamper-evident hash chain (P39) — R170 library + R171 wire-up + R172 verification cron + R173 NOT-NULL flip + R174 chain-only write trigger (warn mode default; block flip after staging soak).
-
Backups (
BACKUP-CAPTURE-001): the production database records 115 completed legacy backup runs between 2026-06-04 and 2026-09-16. These records alone do not verify that their objects remain readable, complete or restorable. The replacement Worker capture is owned byevery-2min-recovery-controland requires an Ed25519-signed, remotely witnessed manifest on both storage legs. On 2026-09-19 the production schedule had no active snapshot, epoch, chain or capture outcome, and there were zero export snapshots, restore preparations and restore runs. No operating, verified replacement capture or provider restore is evidenced. Supabase managed backup/PITR configuration is also unverified: the observer reportsunknownbecause a least-privilegedbackups_readcredential is unavailable. Do not claim verified daily backups, three independent backup locations, a production recovery point or an achieved recovery time from these records. Local fixture signatures and in-memory storage legs are test evidence only. The planned 7-year Object Lock COMPLIANCE archive ofaudit_logsalso remains unverified as a provisioned control; its runbook is a design, not evidence of a working export schedule. -
Recovery boundary (
DR-RESIDUAL-001): the Worker-driven capture replays OBJECT METADATA. It is not a managed whole-database recovery, and it cannot reconstruct authentication. Total loss of the managed database, authentication and point-in-time-recovery domain is unrecoverable for complete authentication even if Cloudflare and R2 survive. This residual is carried under its stable id until an independently administered encrypted authentication export and import leg exists and has been rehearsed; an accepted release decision does not close it. -
Recovery rehearsal: the restore path was rehearsed on 2026-09-11 against disposable local databases and in-memory buckets only (report in
docs/dr-drills/). No provider rehearsal or verified production capture is evidenced; the local rehearsal exercises restore code and fixtures rather than recovery of the production service. No drill cadence is claimed, because none has been kept. -
Vulnerability scanning: GitHub Advanced Security CodeQL + Dependabot + secret scanning + Dependency Review action (R159/R166).
-
Penetration testing: Not yet performed — the first external engagement is in vendor selection. Annual cadence planned once engaged.
-
Coordinated vulnerability disclosure: RFC 9116
security.txtpublished R168 +VDP-policy.mddocumented R157. -
Incident response: RUNBOOK.md §2 covers prod-incident runbooks. GDPR Art. 33-34 breach-notification process: discovery → 72h DPA-notification + concurrent customer notification per DPA.md §8.
-
Data minimization: Edge IP truncation, no raw IPs in
audit_logs, no marketing-tracking analytics on dashboard (CF Web Analytics only — no cookies set). -
Right to erasure (Art. 17): DPO-fulfilled on written request (no self-service deletion tab exists). An Art. 17 erasure does not DELETE audit rows — it ANONYMIZES them in place (nulls
user_id, scrubs PII metadata keys, nullsip_address/user_agent) and re-hashes the chain, because a deletion here would have to be selective and mid-chain. Chain-safe re-hashing coreaudit/anonymize.ts(R691/R693), exposed atPOST /admin/dsar/users/:userId/anonymize(saas-admin + MFA, behindGDPR_ANONYMIZE_ENABLED).- ⚠️ “Anonymized, never deleted” describes the ERASURE mechanism only — it is NOT the lifecycle of the audit trail. Audit rows are also subject to TIME-BASED retention and are HARD-DELETED when they age out: 24 months by default, 5 years for actions prefixed
billing.,sso., orgdpr.. The two mechanisms are independent and both apply. Source of the cutoffs:RETENTION_POLICIESinpackages/shared/src/retention.ts(audit_logs,extendedRetention.valuePrefixes). - Why the 5-year bucket exists: Bokføringsloven §13 requires accounting documentation to be retained for five years. Billing actions are accounting records, so they cannot be purged on the ordinary 24-month schedule;
sso.andgdpr.share the bucket because they are the evidence that access control and data-subject obligations were actually discharged. The carve-out is a legal-retention obligation (Art. 6(1)(c)), not a data-minimization failure. - How deletion stays compatible with the P39 chain:
verify_audit_chainwalks a whole partition from genesis (prev_hash IS NULL) with no checkpoint or stored head-hash, so removing rows would break it. The purge RPCpurge_audit_rows(migration 0126, applied) therefore DELETEs the aged-out rows and, in the SAME transaction, rewrites each surviving row’sprev_hash/hashinto a fresh self-consistent chain — the new oldest survivor becomes a new genesis. It then re-runsverify_audit_chainand raises (rolling the whole transaction back) if the result is not intact. It does not scrub PII; purging removes rows, anonymizing rewrites them. - Current state of the time-purge: code-complete and cron-wired (daily
30 1 * * *,runAuditRetentionPurge) but DISARMED until the operator setsGDPR_RETENTION_PURGE_ENABLED. Noaudit_logsrow has yet reached the 24-month cutoff, so no row is currently overdue for deletion.
- ⚠️ “Anonymized, never deleted” describes the ERASURE mechanism only — it is NOT the lifecycle of the audit trail. Audit rows are also subject to TIME-BASED retention and are HARD-DELETED when they age out: 24 months by default, 5 years for actions prefixed
-
Erasure of an INDIVIDUAL (Art. 17):
POST /admin/dsar/users/:id/anonymizeperforms the complete erasure (R926): it clears every reference theauth.usersforeign keys do not reach and the subject’s email (invitations addressed to them, notification-log recipients, their signup reservation), anonymizes the audit chain, THEN deletes theauth.usersrow so the FK cascades remove memberships and preferences, and finally removes the auth server’s own log entries that name the subject. The list of relations isuser_erasure_manifest()(migration 0421), reconciled against the database catalog by an automated test. Frozen checkout records are retained as accounting documentation (Art. 17(3)(b)); tenant-configured addresses are shown to the operator and not rewritten automatically. Refused for a current Preferium operator and for the only owner of a live tenant. DISARMED behindGDPR_ANONYMIZE_ENABLED: arming is an operator decision about an irreversible action — the destructive path itself is validated by an automated round-trip on a real PostgreSQL database (scripts/user-erasure-postgres.test.mjs). -
Erasure of a TENANT (Art. 17 / DPA §10): the supported path uses a 30-day soft-delete grace window, the scheduled
daily-tenant-purgesweep and the guardedpurge_tenantRPC, including the deletion certificate and the due/holding protections in migrations 0420/0423. The old directDELETE FROM tenantsrunbook path is not the supported replacement. Following explicit operator authorization,TENANT_PURGE_ENABLED=truewas armed and the API deployed on 2026-09-19. Natural execution after arming remains unverified: the latest observed heartbeat, 2026-09-19 13:01:46 UTC, precedes arming; the next scheduled run is 2026-09-20 13:00 UTC. A heartbeat alone does not prove anenabled: truesweep or successful deletion. No accelerated or destructive production test was performed. This configuration change does not establish completion of an erasure request or independently prove the promised deletion deadline. Individual-user anonymization remains a separate control and flag. -
Right to portability (Art. 20): Dashboard + API
GET /domains/:id/exports/csv(current overrides) andGET /domains/:id/exports/deployed.csv(deployed state), plus the DSAR JSON bundle below for cross-tenant requests. There is no/v1/exports/fullendpoint —DPA.md§8 claimed one until 2026-08-06; it was never built. -
Right to access (Art. 15): SaaS-admin-fulfilled via
GET /admin/dsar/users/:userId/export(R685, MFA-gated, downloadable JSON bundle). No self-service “My data” tab exists yet.
F. Children’s data (Art. 8)
The Service is B2B; no part of the product is targeted at children under 16. No age-gate at sign-up because account creation is restricted to natural persons acting on behalf of a business. If a Customer publishes children’s data in their HTML, Preferium processes it as instructed by the Customer — the Customer is the data controller and bears Art. 8 responsibilities.
G. High-risk processing flags (Art. 35)
| Risk category | Triggers Art. 35 DPIA? | Notes |
|---|---|---|
| Large-scale processing of special categories | No | We don’t process Art. 9 special categories (health, biometric, etc.) |
| Systematic monitoring of public areas | No | We crawl Customer’s site (not public areas in the GDPR sense — Customer is the controller) |
| Innovative use of new tech (AI generation) | Yes — DPIA outstanding | The DPIA on the AI optimization pipeline has not been carried out. An earlier revision of this row claimed “DPIA done” while pointing at docs/legal/dpia-ai-pipeline.md, a file that does not exist. |
H. Change log
| Date | Round | Change |
|---|---|---|
| 2026-09-20 | Continuation | Correct standard prompt routing to Workers AI and retain Anthropic for Claude measurement. Reconcile backup claims with read-only production observations; distinguish legacy runs, local rehearsal and unverified provider recovery. Record authorized tenant-purge arming with natural execution still pending. No processing-policy or transfer-mechanism change. |
| 2026-08-14 | W92 | Corrections against code + prod, no change in actual processing. (1) §B row 3 + §C: generative AI processor corrected to Cloudflare Workers AI (source: packages/shared/src/ai-models.ts); Anthropic reclassified to citation-prompt suggestion only; Workers AI + Browser Rendering added to the Cloudflare rows. (2) §A/§I: DPO = Robert Andre Johansen (Option A, decided 2026-08-06) — replaces “pending decision” text; vendor-pricing notes removed from this published page. (3) §E: encryption in transit corrected to TLS 1.2 minimum / 1.3 preferred (prod-verified handshake + min_tls_version: '1.2' in code); pen-test row corrected to “not yet performed”; tenant-erasure note updated — migration 0290 IS applied in prod, TENANT_PURGE_ENABLED still unarmed. |
| 2026-08-06 | P9 | Three contradictions against code corrected. (1) §E “the audit trail is ANONYMIZED, never deleted” described only the Art. 17 erasure mechanism; time-based retention HARD-DELETES at 24mo / 5yr (purge_audit_rows, migration 0126) — both mechanisms now documented, with the Bokføringsloven §13 basis for the 5-year bucket written down. (2) §E tenant erasure split out and marked NOT EXECUTABLE in prod (#765; migration 0290 unapplied) instead of implying the runbook path works. (3) §E backups: the 7-year Object Lock archive is documented as designed-not-provisioned — the bucket does not exist. Row 6 retention aligned to the actual billing./sso./gdpr. prefixes; portability corrected (no /v1/exports/full). |
| 2026-05-21 | R174 | Template created. Sub-processor list pulled from docs/legal/sub-processors.md (created same round). DPIA placeholder added. |
| 2026-05-19 | R157 | DPA.md drafted with §9 sub-processor change notification mechanism. This Art. 30 template was the natural follow-up. |
I. Operator notes
Per docs/legal/dpo-designation.md, Preferium has not appointed a DPO. Robert Andre Johansen is the operational Privacy Contact, not an independent Article 37 DPO. If the assessment or appointment changes, update §A and every public legal surface in the same legal-set version.
Where this lives:
- Primary canonical:
docs/legal/gdpr-article-30-records.md(this file, version-controlled). - Per-tenant copy: each Customer can request their own Art. 30 record extract — generated from this template + their specific use of the Service (sub-processors they’ve consented to, plan tier, custom integrations).
- Customer-portal download: does not exist. There is no Compliance tab and no self-service Art. 30 export in the dashboard. Requests go to the Privacy Contact by email. This record and the Trust Centre page both carried a dashboard-export promise, under two different internal round numbers, for work that was never scheduled.
When this template is updated, the change MUST be reflected in:
- A Robert-selected plan — if the change affects planned work (MASTERPLAN-2 is archived)
enterprise-readiness.md§C (skill file)dpa-redlines.md(tracker)- This file’s §H change log