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.
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
| Endpoint | What 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.
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 = "|".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.
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
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.