Methods · consent ledger

Provenance you can verify, not a badge you have to trust.

Rights-cleared and consented is what everyone in this market says. This page is the part that comes after the sentence: the exact bytes we hash, the order we hash them in, and the endpoint that returns the chain — so you can recompute our records yourself and catch us if we are wrong.

A certification attests that a third party once reviewed a process. This produces evidence about each individual clip. Those are different products, and only one of them survives an audit of a specific delivery — which is the audit you will actually be asked for.

Delivery formats — what we do and do not offer

We deliver LeRobot v2.1, HDF5 (EgoDex-style paired files) and the open FH-Ego v1 JSON schema; Zarr v2 and TFRecord (RLDS-style) are produced on request. Every format is verified by an automated export bench before we quote it. We do not offer MCAP or WebDataset — MCAP is a robotics logging container rather than a training format, and labs shard WebDataset internally; we would rather tell you which is which.

Verify a delivery

This is the part that matters on the day you are asked to justify a dataset. There are two routes to it, and the second one does not need us at all.

The endpoints below need a credential, and today nobody holds one. We are pre-revenue with no customers, so no Commissioning-lab account exists yet — these are what you would be issued on the first engagement, described here so you can judge them before there is anything at stake. The route that needs nothing from us is reproducing the hash yourself from the published pre-image, further down this page. If you only read one section, read that one: it is the only check that still works on a day we would rather it did not.

Verification endpoints

EndpointWhat it returns
GET /api/v1/consent/verify Recomputes every hash and checks the linkage. Returns the genesis hash, the head hash, the number of entries checked, and — if broken — the exact sequence number and reason.
GET /api/v1/consent/receipt/{id} The full append-only history of one receipt, oldest first: granted, then withdrawn, each entry with its own hash and link.

You do not have to take our word for the result, and you do not have to use our endpoint at all — recompute the hashes yourself from the published specification and compare.

How it works

Append-only

Withdrawal of consent appends a REVOKED entry; it never edits the original. The grant and the withdrawal both remain in the ledger, each independently hashed. That matters because "we deleted the record" and "we can show you exactly when consent was withdrawn" are not the same claim, and only the second one is auditable.

Append-only is enforced by the database itself — consent_ledger_no_update, consent_ledger_no_delete abort any UPDATE or DELETE — not merely by convention in application code.

The footage is a different matter, and it does get deleted. Revoking a receipt runs a deletion stage over the raw video, the redacted copy, the working files and the annotation, and halts any processing job that was mid-flight. The ledger entry recording that you withdrew survives — it is the evidence the request was honoured — along with a fingerprint of the bytes that were removed. What the ledger keeps is the proof; what it does not keep is the clip.

Hash-chained

Each entry's hash includes the previous entry's hash. Removing or reordering an entry breaks every hash after it. The first entry links to a published genesis value, so an auditor starts the walk from a known constant rather than from something we supply.

Genesis hash · SHA-256 · 64 hex characters
0000000000000000000000000000000000000000000000000000000000000000

That is GENESIS_HASH = "0" * 64 — a constant, not a value we mint, so the starting point of the walk is not ours to change. Scroll the block sideways rather than reading a wrapped copy: a hash broken across lines cannot be compared character-by-character, and that comparison is the whole exercise.

A defect we found here, and fixed

This sits inside the mechanism rather than in a section of its own, because it is the reason the pre-image below contains what it contains. A security claim from a vendor who has never described a mistake is not worth much.

Until 12 August 2026, the hash covered receipt_id : worker_id : task_id : timestamp : device_id. It did not include status or revocation_timestamp — the two fields that carry the revocation claim. Editing a row from REVOKED back to ACTIVE left the stored hash still verifying. Records were also updated in place, destroying prior state, and each row was hashed independently, so a deleted row left no trace.

The earlier implementation could not detect the precise tampering it existed to prevent. It was found during internal review before any Capture Partner data had been recorded — the database was empty, so nothing required migration. The specification below is the fix, and the two fields it was missing are named in it.

Reproduce it without us

SHA-256 is computed over these fields, in this order, joined with |. This block is the complete specification — given the rows, you can recompute every hash in any language and compare, with no cooperation from us and no access to anything we control.

Pre-image · field order
pre_image = "|".join([
    sequence,
    receipt_id,
    worker_id,
    task_id,
    status,
    consent_timestamp,
    revocation_timestamp,
    device_id,
    location_city,
    dpdp_pii_policy,
    prev_hash,
])

sha256_audit_hash = sha256(pre_image.encode("utf-8")).hexdigest()

Every semantic field is inside the pre-image, including status and revocation_timestamp — the two the 12 August defect had left out. Altering any one of them changes the hash. The separator cannot occur in the generated identifiers, so two different records cannot collide by concatenation.

One entry, worked end to end

A specification you cannot run is a specification you have to trust, so here is a complete entry: every field, the pre-image they join into, and the hash our service produces from it.

The record is an example and its identifiers say so. A real row carries a Capture Partner's id, their device and their city — publishing one would be the exact disclosure this ledger exists to prevent. The hash is not an example. It comes from the same compute_entry_hash that hashes production records, and this page refuses to build if the field order printed above stops reproducing it.

Example entry · pre-image · SHA-256
sequence              1
receipt_id            CONSENT_RCPT_EXAMPLE01
worker_id             USR_EXAMPLE_0001
task_id               TASK_EXAMPLE_0001
status                ACTIVE
consent_timestamp     2026-08-17T09:30:00Z
revocation_timestamp  (none)
device_id             DEV_EXAMPLE_0001
location_city         Bengaluru
dpdp_pii_policy       Automated face redaction (YuNet) server-side at ingestion, before any human review — fails closed. Faces only; licence plates not covered.
prev_hash             0000000000000000000000000000000000000000000000000000000000000000

pre_image  (the eleven fields above, joined with |)
1|CONSENT_RCPT_EXAMPLE01|USR_EXAMPLE_0001|TASK_EXAMPLE_0001|ACTIVE|2026-08-17T09:30:00Z||DEV_EXAMPLE_0001|Bengaluru|Automated face redaction (YuNet) server-side at ingestion, before any human review — fails closed. Faces only; licence plates not covered.|0000000000000000000000000000000000000000000000000000000000000000

sha256_audit_hash
8456edb7402758c2106b81ce96628a601dff3525e1e4ff9ee1820de8d5b9761b
Reproduce it — no dependencies, nothing of ours
printf '%s' '1|CONSENT_RCPT_EXAMPLE01|USR_EXAMPLE_0001|TASK_EXAMPLE_0001|ACTIVE|2026-08-17T09:30:00Z||DEV_EXAMPLE_0001|Bengaluru|Automated face redaction (YuNet) server-side at ingestion, before any human review — fails closed. Faces only; licence plates not covered.|0000000000000000000000000000000000000000000000000000000000000000' | shasum -a 256

That prints 8456edb7402758c2106b81ce96628a601dff3525e1e4ff9ee1820de8d5b9761b. Change one character of one field and it does not — which is the entire mechanism, in a form you can run on your own machine in the next thirty seconds. If it prints something else, one of us has a bug, and it is the kind we want to hear about.

Or check it here, without leaving the page

Your browser hashes the pre-image with the Web Crypto API it already ships, and compares the result to the digest above. Nothing is sent anywhere, and nothing about the answer comes from us.

The exact string being hashed
1|CONSENT_RCPT_EXAMPLE01|USR_EXAMPLE_0001|TASK_EXAMPLE_0001|ACTIVE|2026-08-17T09:30:00Z||DEV_EXAMPLE_0001|Bengaluru|Automated face redaction (YuNet) server-side at ingestion, before any human review — fails closed. Faces only; licence plates not covered.|0000000000000000000000000000000000000000000000000000000000000000

Published digest

8456edb7402758c2106b81ce96628a601dff3525e1e4ff9ee1820de8d5b9761b

11 assertions run against the shipped hash function — not a copy of it — and 11 pass. Four of them matter more than the rest: they bypass the database triggers entirely and tamper with the file on disk, because an attacker with disk access would. The chain still catches it.

What this does not do yet

It is SQLite. A single local file, not replicated, with no production backup policy. Adequate for a pilot; it is not yet the durable store this needs to be, and the migration is on the plan rather than done.

We do not publish the head hash externally. An administrator who rewrote the entire chain from genesis would produce something internally consistent. Anchoring the head somewhere we cannot alter — a public timestamp or a notarised digest — is the fix, and it is not built.

A Capture Partner cannot yet withdraw from the app. The endpoint exists and works, and a withdrawal deletes the footage — but the control that lets a partner press it themselves is not built into the capture app yet, so today a withdrawal reaches us as a message rather than a tap. That is the wrong way round for a right, and it is the next thing we are building.

We hold no third-party attestation. No SOC 2, no ISO 27001. We would rather be judged on a mechanism you can check than on a badge you cannot, which is why the pre-image above is published in full.

Those four are on this page for the same reason the defect above is: a list of limits you can read is worth more to you than an assurance you cannot test. None of them changes what the mechanism does today — every clip we deliver carries a receipt whose hash you can recompute yourself, which is a claim no badge makes.