Skip to content

GDPR & personal data

Where personal data can live in a lumnik installation, what erasure does today, and what is not built.

lumnik is self-hosted software, not a service: no data reaches a lumnik cloud. You remain the controller or processor of the data it touches. This page is an engineering map, not legal advice.

The posture, in three sentences

  • Nothing leaves your infrastructure by default. Chat and embeddings run on a local model runtime (Ollama). Once you configure a cloud LLM provider, questions and grounding chunks are sent to it.
  • lumnik is a read-only mirror. Ingestion never writes back. Ask-to-SQL runs under a read-only database role behind its guards. The hub keeps no conversation history.
  • Isolation is enforced in the database. PostgreSQL Row-Level Security per tenant (RLS); chat, search and entity reads are also bound per business scope.

Where personal data can live

Store What lands there Lifecycle
identity schema user accounts: login, display name, email archive or soft-delete; the row stays until an operator deletes it
connector.t_* mirror tables whatever your sources contain kept until the connector is deleted; see the per-type list below
rag.chunk corpus text chunks and embeddings derived from ingested rows a changed row adds a chunk, the old one stays; deleting the connector removes every chunk it produced
Dead letters the raw payload of rows a run set aside, for any reason kept with the connector; lm dlq discard marks an entry handled and keeps the payload
Event outbox entity name, a key hash, the operation purged nightly past LUMNIK_EVENTS_RETENTION_DAYS (default 30)
Audit trail the acting user and what they wrote, read or exported. What it records purged nightly past LUMNIK_AUDIT_RETENTION_DAYS (default 365, 0 = forever). No per-row erasure
Webhook payloads the column values a kind: Process template references, posted to the tenant's target URL (n8n, Make, your endpoint); HMAC-signed when a secret is set, otherwise protected only by that URL's TLS leaves lumnik; retention is the receiver's
Webhook delivery history the posted payload and the receiver's response body, plus status and attempts purged nightly past LUMNIK_WEBHOOKS_DELIVERY_RETENTION_DAYS (default 90, 0 = forever)
Keycloak its own user store: credentials, sessions your realm, your policies
Backups whatever you back up yours entirely

How a change at the source reaches the mirror tables:

Connector Changed row Row deleted at the source
jdbc-generic replaced by source key stays in the mirror
rest-generic (GraphQL, SOAP included) replaced by its unique_key stays in the mirror
csv-file reading one local file (no glob) replaced: the old version is removed by the next run that reads the whole file; a run that would remove more than 30 % of the rows is refused (refresh.max_removed changes the share) removed the same way
csv-file reading a folder, a glob, SFTP or S3; kafka added as a new row; the old version stays stays in the mirror

The rights, in practice

Access and portability

  • lm entity get gives structured access to one person's rows; read-only SQL covers the rest.

    $ lm entity get Customer --filter email=jeanne.martin@example.com
    CUSTOMER_ID  NAME           EMAIL                      CITY  _CONFLICTS
    C-1001       Jeanne Martin  jeanne.martin@example.com  Lyon  {}
    
  • No one-click DSAR export. Assembling an answer is a query your integrator writes.

Rectification

  • Fix the data in the source. On a jdbc-generic, rest-generic or one-file csv-file connector, the mirror follows on the next run. On the others, the old value stays in the mirror next to the new one: erase it as below.
  • The corpus gains a chunk for the corrected row and keeps the chunk carrying the old value until the connector is deleted and re-ingested (below).
  • Ingested rows cannot be edited in lumnik.

Erasure of a person in your data

flowchart LR
  s["1. erase in the<br/>source system"] --> e["2. lm entity delete<br/>(every tenant)"]
  e --> d["lm connector delete"]
  d --> t["mirror tables<br/>purged"]
  d --> c["every RAG chunk<br/>it produced, removed"]
  d --> r["3. re-apply,<br/>re-ingest"]
  1. Erase in the source first; it is the system of record. Save the entity and SearchIndex manifests you will re-apply in step 3: no lm command gives them back once deleted.
  2. Delete every entity that reads the connector's tables (lm connector delete names it), then delete the connector.
  3. Re-apply the connector, the entity and its SearchIndex, then re-ingest.

An entity is shared by every tenant

Deleting it drops its view and every SearchIndex over it in all tenants, until step 3 re-applies them. A SearchIndex belongs to one tenant: each tenant re-applies its own. Views, app screens and Search over that entity are empty in the meantime.

$ lm connector delete erp-customers --dry-run
Error: API error 409: table connector.t_erp_customers feeds entity customer — remove the entity first (lm entity delete customer)
$ lm entity delete customer
Type 'delete' to delete entity customer — its view, for every tenant, and search index customers: delete
deleted: customer
$ lm connector delete erp-customers
Type 'delete' to delete connector erp-customers (csv-file) — 3 rows of connector.t_erp_customers, its run history and RAG chunks: delete
deleted: erp-customers
lumnik=# SELECT count(*) FROM connector.t_erp_customers;
ERROR:  relation "connector.t_erp_customers" does not exist
  • A table another connector also writes keeps that connector's rows.
  • Deleting rows from connector.t_* by hand is not enough: their chunks stay until the connector itself is deleted.

Erasure of a lumnik user account

  • DELETE /api/tenants/{tid}/users/{id} archives (archived_at); SCIM DELETE soft-deletes (deleted_at). lm has no command for either. Deactivate vs erase
  • Either way the next request is refused with 403, valid token or not, and the account drops out of listings.
  • Neither deletes the row, and neither removes anything from the audit trail. Audit rows go only with the nightly retention purge.

Not built yet, recorded not hidden

  • No automated hard-delete or pseudonymisation of user accounts. An archived or soft-deleted user stays in the identity schema until an operator deletes the row.
  • No DSAR export endpoint. Access is manual.
  • No deletion propagation from sources to mirror tables. Erasure is the rebuild above.
  • No API to erase a dead-letter payload.
  • The audit trail is narrow: no SCIM writes, nothing outside its table, no read surface, no per-row erasure. Not recorded