GDPR

What Arkeion does for your data-protection obligations, what it leaves to you, and the one place where the design actively works against you. No product can make you compliant — compliance is a property of your processing, not of a database. So this page maps mechanisms to obligations and then says, in plain words, where the mechanism runs out.

This is not legal advice. It is a description of what the code does, written so your counsel can check it against the source.

Who is responsible for what

The GDPR assigns roles, and the role decides who owes what. Arkeion ships in two shapes, and they sit on opposite sides of that line.

Data subject the person You — controller you decide why and how Self-hosted engine no processor · no agreement · no third party Arkeion Cloud Syrakon is your processor · DPA · sub-processors
The upper branch is the strongest privacy posture we can offer, and it is the free one.

If you embed the engine or run it yourself, we are not a processor. We never receive your data, there is no agreement to sign, and there is nothing about us you need to trust. The engine is MIT or Apache-2.0 and the file format is published, so nothing in that arrangement depends on our continued existence.

If you use Arkeion Cloud, Syrakon processes personal data on your instructions and everything in Article 28 applies to us: a written agreement, a sub-processor list, assistance with data-subject requests, and deletion or return at the end.

The erasure problem

Read this section twice before you put personal data in Arkeion. It is the one place where the engine’s central design decision works against a legal obligation, and no amount of wording makes it go away.

Arkeion is append-only with copy-on-write. Updating a row does not overwrite the old row — it writes a new version and keeps the old one reachable through AS OF. That is the whole point of the product, and it is exactly what Article 17 does not want.

There is precisely one tool that removes history: vacuum, with a retention policy. It takes three forms — keep everything, keep the last n versions, or keep everything since a timestamp. All three are a global frontier.

Before — every version reachable by AS OF v1v2v3 v4v5v6 v7v8v9 subject Asubject C subject B frontier must sit here — B's last version is v6 vacuum — keep from v7 on After — and look what else went with it subject Asubject C subject B B erased, as asked. A lost half its history and C two thirds — neither of them asked for anything.
The dashed outlines are versions nobody requested the deletion of. The frontier does not know what a data subject is.

The consequence is blunt. To guarantee that subject B’s historical versions are gone, the frontier must sit above B’s last version — which discards every other row’s history below the same line. Complete erasure of one person, with no assumptions about where their data appears, means KeepLast(1): throwing away the entire history of the database. There is no per-row, per-key or per-subject purge, and we are not going to imply otherwise.

Two operational details that matter if you plan to promise anyone an erasure deadline:

  • vacuum refuses to run while any branch other than main exists. An erasure request is blocked until you merge or drop the others.
  • vacuum returns busy if a write transaction is open, and it rewrites the whole file, so it needs roughly twice the database size in free space while it runs.

What “physical erasure” honestly means

vacuum builds a new file containing only the versions above the frontier and renames it over the old one. Pruned versions are never written into the new file, and AS OF below the frontier returns a version-not-found error rather than data. That much is real, and it is more than a deletion flag.

But the old file is unlinked, not shredded. Nothing overwrites those blocks. On a copy-on-write filesystem, a snapshotted volume, or an SSD doing wear levelling, the previous bytes can survive on the medium long after the rename. So the accurate sentence is “removed from the database file” — not “destroyed on the disk”, and not “verifiably erased”. The vacuum report is our own count of what it dropped, not an attestation, and no test asserts that erased content is absent from the new file.

The pattern that actually works

The way out is not a feature request — it is a schema decision, and you have to make it before you write the first row.

identity opaque_id → name, email, address small · short retention vacuum KeepLast(1) is cheap here erasure deletes the row and the history history opaque_id, event, amount, timestamp large · append-only · full AS OF no names, no free text about people untouched — integrity survives opaque_id break this link, not the chain
Keep people in a table you can guillotine. Keep the history keyed by something that means nothing on its own.

Split identity from events. The identity table is small, holds the mapping from an opaque identifier to the actual person, and gets an aggressive retention policy — collapsing it to a single version costs almost nothing. The history table is the big append-only one and never contains a name, an email, or a free text field where someone will eventually type one.

An erasure request then deletes the mapping and vacuums the small table. The history keeps its hash chain and its full version record, and nobody can tell whose it was.

One caveat, because this is where the argument is usually oversold: this only reaches anonymity if re-identification is genuinely not reasonably possible afterwards. A history row carrying a postcode, a birth date and a rare diagnosis can identify someone with no name attached. Pseudonymised data is still personal data — the pattern moves you from “impossible” to “tractable”, not from “impossible” to “exempt”.

Crypto-shredding, and exactly what it buys

The standard answer to “append-only versus erasure” is crypto-shredding: don’t delete the data, destroy the key, and the ciphertext becomes noise. It is a good answer. It is also routinely claimed by products whose key granularity cannot support it, so here is precisely where ours does and does not.

The engine has one key per database. Encryption is page-level in the sense that a page is the unit sealed by each operation — one AES-256-GCM cipher per file, with the nonce as a per-page counter. It is not one key per page. And a page is a B-tree node holding rows from whoever happens to sort nearby, so the unit of encryption and the unit of erasure do not line up. Destroying the key does not remove a person; it removes the database.

That rules out shredding one data subject inside the engine. It does not rule out crypto-shredding — it moves the key boundary to the two places where it actually works.

One key per tenant — this is the one that solves backups

A managed database is a file, and a file takes a key. Give each tenant their own and destroying it makes their entire database unreadable everywhere that file exists — including in every copy taken before the request, on media nobody can enumerate any more.

That matters because backups are the part of Article 17 that most vendors quietly skip. A retention policy cannot reach a snapshot taken three months ago; a destroyed key can. It is end-of-relationship erasure — a departing customer, a terminated contract — not per-person erasure, and it is genuinely strong at that job.

One key per subject — above the engine, not inside it

When you must keep identifying data in the append-only history, encrypt it in your application under a key belonging to that person, and keep the keys somewhere you can really delete.

While B is a customer keyring tiny · mutable · KeepLast(1) is cheap A → key_aB → key_bC → key_c decrypts history append-only · every version kept forever A ▸B ▸C ▸ B asks to be erased — delete key_b, vacuum the keyring After — nothing was deleted from the history A → key_aC → key_c B → gone, and not in any copy A ▸B ▸C ▸ noise — no key exists
The history keeps its hash chain, its versions and its integrity. What it stops keeping is the ability to read one person.

The keyring is small by construction — one short row per subject — so the retention frontier that is ruinous on a big table is trivial here: collapsing it to a single version costs almost nothing and takes no history worth having with it. That is the same trick as the pseudonymisation pattern above, one level stronger: there, you kept identities out of the history; here, you keep them in it but unreadable.

The four things that have to be true, or it is theatre

The key must not survive anywhere. Back up the keyring and you have undone the erasure without noticing. The keyring is the one thing in your system that must not be in your backups, and that is a deliberate, documented exception, not an oversight.

Keys must not be derivable. A per-subject key computed from a master secret plus the subject id is not shredded when you delete the row — anyone holding the master recomputes it. Generate them randomly and store them; do not derive them.

Metadata survives, and it is still personal data. Crypto-shredding destroys content, not existence. That a record existed, when it was written, how often it changed, how large it was and what it linked to all remain in the clear. If the shape of the data identifies someone on its own, this does not save you.

The legal status is defensible, not settled. The common reading is that data nobody can decrypt is effectively erased, and it is the position most of the industry operates on. It is not a universal regulatory ruling, and a supervisory authority can take a stricter view. Design for it, document it, and do not let anyone tell you it is a closed question.

Where this stands today

The per-subject pattern works right now, because it lives in your application: the engine only ever sees ciphertext in a column. The per-tenant pattern needs the daemon to accept a key, which it does not — arkeiond takes none, so a served database is plaintext on disk. Until that changes, tenant-level crypto-shredding is available to embedded and self-hosted deployments, and not to the managed service.

What the hash chain proves

Every commit hashes the previous chain value together with the content hash, version, timestamp and roots. verify() walks from genesis to head and reports the exact version where anything fails. Flip one byte in one historical page and it surfaces.

Edit without the chain one byte changed in a past page verify() → ChainBroken at v4 detected, with the exact version Rebuild from genesis whoever holds the file recomputes every link verify() → passes cleanly unless you kept an anchor from before
Tamper-evident against someone without the file. Not tamper-proof against someone holding it.

The chain is an unkeyed SHA-256 over public fields. There is no secret in it, no signature and no message authentication code. Anyone holding the file can recompute the whole chain from genesis after changing whatever they like, and verify() will pass. The word for this is tamper-evident, and we will not use immutable or tamper-proof anywhere on this site.

The mitigation is an audit anchor: a version number plus the chain hash at that version, forty bytes you capture and keep. Later, verifying against that anchor detects any rewrite of the history below it. Its entire value comes from where you store it — if it lives next to the database, it proves nothing.

Two limits worth stating: nothing is notarised, timestamped by a third party, co-signed or published to a transparency log, and the anchor check is not currently reachable through the network protocol, so it is a discipline for embedded and operator use today.

Encryption, exactly where it applies

The engine encrypts pages with AES-256-GCM when you supply a key. You pass thirty-two raw bytes; the engine has no key store and no derivation function, by design — custody stays with you. Each file derives its own subkey, so reusing a master key across tenants cannot collide nonces, and keys can be rotated by rewriting the file.

What this does not cover matters just as much:

  • The daemon takes no key. Anything served over the network today is unencrypted on disk. Encryption at rest for a hosted product is a prerequisite we have not shipped, and until we do, no page here will claim it.
  • There is no KMS, HSM or Vault integration. “Bring your own key” here means literally that you hand over the bytes. It is not enterprise key management.
  • The unit is the page, and there is one key per database. Crypto-shredding an individual person is therefore impossible; destroying a key destroys the whole database, not one subject.

Article by article

Obligation What Arkeion gives you What stays yours
Art. 5(1)(e) storage limitation Retention policies enforced by compaction The default keeps everything — you must set a policy on purpose
Art. 5(2) accountability History is queryable evidence, not a claim Deciding what the evidence needs to show
Art. 15 access SELECT over the current or any past state Assembling the response; there is no export command
Art. 17 erasure A global retention frontier Schema design — see the section above
Art. 20 portability Single file, publicly specified format, third parties can write readers Converting to whatever format the recipient wants
Art. 25 by design Pseudonymisation is natural if you split identity from history Actually doing the split
Art. 30 records Schema and version history Access records — we do not log reads
Art. 32 security AES-256-GCM at rest (embedded), TLS in transit, argon2id credentials, per-branch permissions Key custody, and everything above the database
Art. 33 breach scope AS OF states exactly what existed at a given moment Notifying, within the deadline
Art. 44+ transfers EU hosting, French jurisdiction, European ownership Your own onward transfers

Questions from sceptical buyers

“Append-only and the right to erasure are incompatible. How do you square it?” For the most part we don’t, and the section above says so with a diagram. The frontier is global. The workable answer is to keep identifying data out of the append-only history in the first place, which is a schema decision you make on day one and cannot retrofit cheaply.

“Is vacuum real erasure or a flag?” Real, in the sense that pruned versions are never written into the new file and become unreadable through AS OF. Not real in the sense of shredding: the old file is unlinked, not overwritten, and its blocks may persist on the medium.

“You say you don’t access our data. You’re root on that machine.” Correct. On the managed tier, “we don’t access it” is a commitment backed by isolation and an audit trail — it is a policy, not a physical impossibility. The version where we cannot read it needs a confidential enclave, and that is on the roadmap, not shipped. We keep those two words apart everywhere on this site because the difference is the entire point.

“Is my database encrypted at rest in Cloud?” Not today. The daemon takes no key. That is a gap we have to close before a hosted product can be sold on a security argument, and we would rather say it here than let you discover it in a security review.

“Can you crypto-shred a single customer?” Not inside the engine: one key per database, and a page holds rows from many people, so destroying the key destroys the database rather than a person. You can do it above the engine with a per-subject key and a keyring you actually delete — the section above sets out the pattern and the four conditions that have to hold for it to mean anything. At tenant granularity it works directly, and that is the version that reaches your backups.

“Your chain is unsigned. I could rewrite it.” If you hold the file, yes — and so could we. That is why it is called tamper-evident. Capture an anchor, store it somewhere we cannot reach, and the guarantee becomes real for everything below that point.

“Do you log who read what?” No. There is no access logging today, reads leave no trace, and commits carry no actor identity — the chain records what changed and when, never who. If your obligations need access records, you must produce them above the database.

“How do I delete from your backups?” There is no backup product, and nothing propagates a deletion into copies that already exist. This is a hard problem for everyone in the industry; we would rather admit it than pretend a retention policy solves it.

“Are you ISO 27001 or SOC 2 certified?” No, and we have not applied. If a certification is a hard requirement for you, we are not a fit yet.

“What happens to our data if Syrakon disappears?” The engine is MIT or Apache-2.0, the file format is publicly specified on this site, and your database is a single file you already hold. You can keep running it, or write your own reader from the specification. This is the one continuity question we can answer without asking for any trust at all.

“Sub-processors, and the US CLOUD Act?” Hosting is European under French jurisdiction, and ownership is European — which is the part that actually matters, since a US-owned provider is exposed regardless of which region you pick. Cloud is not open yet; when it opens, the sub-processor list is published with the agreement.

“Can we get everything out?” Copy the file, read it with an independent implementation, or SELECT it out through the driver. There is no one-command export and no CSV, JSON or SQL data dump today — the schema dumps, the data does not.

“Do you phone home?” No telemetry, no usage reporting, no network calls the engine makes on its own.

What we do not claim

  • Not “GDPR compliant” — no database can sell you that.
  • Not immutable, not tamper-proof: tamper-evident.
  • Not notarised, anchored by a third party, or signed.
  • Not encrypted at rest when served over the network, today.
  • No per-subject erasure inside the engine, and no key management product. The crypto-shredding above is a pattern you implement, not a feature we ship.
  • No access logs, no managed backups, no one-click export.
  • No security certifications.

Everything on this page is checkable against the source, which is the only reason to believe any of it. If you find a claim here that the code does not support, that is a bug and we want to hear about it.