.ch
Governed by Swiss law · Zürich
The Landwasser viaduct
Verify

History that cannot be rewritten. Not quietly, not at all.

Every write and every export read appends an audit row hashed over the one before it — SHA-256 over ciphertext, so the chain verifies with no key at all. A read that is not on the chain did not happen.

Landwasser Viaduct · Albula line · 1902

Three levels of proof — and a witness for the fourth.

A file holder can check the chain. A key holder can check every row without opening one. The operator can open everything and cross-check it against the log. Whole-file rollback — the one thing a file cannot see about itself — is caught outside it.

without a key
verify_chain

Walk the audit chain in sequence. A gap, a broken link, or a modified row is named — by seq. Removed, reordered or edited: it says which.

vault key · no content keys
verify_store

Recompute every checksum, prove every seq gapless, every version sequence dense, every wrapped key present exactly when it should be — without decrypting a single record.

everything
verify_deep

Decrypt every current record and cross-check it against the audit log — one event per revision, no more, no fewer. Rollback forgery fails three ways: AAD, checksum, missing event.

The witness.

Anchor the chain head — seq and row hash, metadata only — somewhere the file's writer cannot reach, and reconcile on a schedule: Match, Truncated, or Diverged. Fail-open by design: a witness outage never blocks a local write.

Reads are audited.

Every public API that returns record or object data — get, list, history, search, children, objects — seals a success event before returning. Fail-closed on a writable store: no audit, no data. Empty results still leave a trail: an enumeration probe that found nothing is still a probe.

History marks, never drops.

A revision that fails its checksum, fails to decrypt, or was shredded appears in history as a visible marker — never as authentic data, never silently omitted. Nothing on the read path returns an unverified record.

Erasure without residue.

Crypto-shred is the one physical DELETE in the engine: the record's wrapped key is zeroed in place and read back to prove it, then its index rows and object names are purged. Rows and chain stay byte-intact and verifiable; the content — and every object sealed under it — is unrecoverable. The fact of erasure stays auditable; the subject does not: events carry node ids only. Works on tombstoned records too, the common GDPR case.

Anatomy

What a chain row is.

Not a log file. Each row is a sealed record and a link, written in the same transaction as the thing it attests. Everything an investigator needs is inside the seal; everything a verifier needs is outside it.

Sealed per event.

The payload — action, actor, actee, outcome, reason, version, client address, grant coordinates, object id — is AES-256-GCM sealed under a key derived for that one event from the store's master secret and the event id, bound to the event id and timestamp. Subjects are node ids, never names: after a shred the subject is unresolvable and the chain stays whole.

Chained over ciphertext.

row_hash = SHA-256(prev_hash | event_id | created_at ‖ ciphertext). Because the hash covers the sealed bytes, verify_chain proves the log was not edited, thinned or reordered — with no key at all.

Same transaction as the write.

Data row, index rows and audit row commit together or not at all. There is no "write succeeded, log missed".

Denied is on the same chain.

A refused read, write, grant or shred is an event with outcome denied — not a side table. On a write path the open transaction is dropped first, so the denial survives the rollback. Uniqueness probes and integrity failures are outcome failure. Ordinary version conflicts are deliberately not audited: retry noise would drown the signal. Identical rejections coalesce for sixty seconds, the next event carrying the suppressed count.

Batches are one event with sealed members.

A batch write appends one put_batch event whose sealed member list names every record and version; verify_deep expands it, so each member is as anchored as an individually audited write.

How you read it.

audit_query, newest first, paged; filtered by time and by the sealed entry reference — never by the plaintext column, which sits outside the row hash and could be retargeted by a key-less writer. Reading the log is not itself an export and writes no event.