What a BurnLedger certificate asserts — and what it does not

Status: in force. This document is incorporated into the Terms of Service as Schedule A, and the Terms have been in force since 2026-08-28 — so it binds, and describing it as a draft understated what a customer has already agreed to. Engineering wrote it from the running system, not from the marketing copy, and published it without prior review by outside counsel; that review is pending, on the same footing as the other published documents. The factual claims below are ours and are testable.

This document is the source text for three places that must not disagree:

  1. the limitations block in the PDF (service/pdf.go);
  2. a scope object inside the signed certificate JSON;
  3. the plain-language summary a customer sees before they rely on one.

1. What BurnLedger verified

For each system the customer registered, at two moments in time:

  • At attestation: BurnLedger connected using a credential the customer supplied, ran a query template the customer wrote, and recorded how many records matched.
  • At verification: BurnLedger ran the same query again and recorded that zero records matched.

Both counts are measured inside an AWS Nitro Enclave and signed there. The certificate names each system, its connector type, the number of records found at attestation, and zero at verification.

1.1 What one record is, for each connector type

"Record" is not one unit. Each connector counts the natural unit of the system it queries, and those units are not comparable with one another: a certificate covering a Redis system, an S3 bucket and a PostgreSQL table reports three counts of three different things. The unit is fixed by the connector type, and this table is the definition.

connector_type One record is
postgresql one row of the customer's SELECT result
mysql one row of the customer's SELECT result
sqlserver one row of the customer's SELECT result
oracle one row of the customer's SELECT result
redshift one row of the customer's SELECT result
snowflake one row of the customer's SELECT result
databricks one row of the customer's SELECT result
teradata one row of the customer's SELECT result
bigquery one row of the customer's SELECT result
cassandra one row of the customer's CQL SELECT result
neo4j one row returned by the customer's Cypher query — a node, a relationship or a projection, whichever that query returns
mongodb one document in the one configured collection matching the customer's filter
elasticsearch one document in the one configured index matching the customer's query
marklogic one document matching the customer's structured query
dynamodb one item returned by a key-condition query against the configured table or secondary index
redis one key whose name matches the customer's pattern — not one field, element or member held inside that key
hbase one row whose row key starts with the customer's prefix, in the one named table
s3 one object under the customer's key prefix
gcs one object under the customer's key prefix
azure_blob one blob under the customer's key prefix

Two things follow from the table, and neither is a hedge.

Counts from different connectors do not add up. A certificate reporting 3 records for a Redis system and 3 for an S3 bucket is not reporting six of anything. Each number is comparable only with the same system's number at the other moment in time, which is the only comparison the certificate makes.

For the query-shaped connectors, the customer defines the unit. The nine SQL connectors, cassandra and neo4j count the rows the customer's own statement returns, and BurnLedger does not constrain that statement's shape beyond requiring a single read-only read. A JOIN can multiply the rows a person occupies, a GROUP BY or DISTINCT can collapse them, and an aggregate returns a row whatever the underlying data is — a template built around count(*) returns one row at attestation and one row again after every underlying row is deleted, so verification never reaches zero and no certificate issues. What the number counts is a property of the customer's query. BurnLedger does not audit it and does not record it.

One further exception is worth naming: for s3, in a bucket where versioning is enabled or suspended, BurnLedger refuses to produce a count at all while any non-delete-marker version of a matching object remains, and counts delete markers otherwise. gcs and azure_blob refuse in the same way when the store still holds a soft-deleted or archived copy. That refusal is the behaviour described in section 2 under recoverable data.

That is the whole of the factual assertion. Everything below is a limit on it.


2. What the certificate does not assert

These are not lawyerly hedges. Each corresponds to something the system genuinely does not do, and a reader who assumes otherwise will be wrong.

It does not assert that the customer's query found everything. The query template is written by the customer. BurnLedger executes it; it does not audit whether it captures every copy of a data subject's information. A query scoped to one table proves something about one table.

It does not cover systems that were not registered. A certificate speaks only about the systems named in it. Data in a system nobody registered is outside it entirely. That includes copies held by your own vendors and sub-processors — a CRM, a support desk, an analytics pipeline: a copy held by any party other than you is covered only if that party's system is itself registered as a system in its own right, with its own read-only credential, and measured.

It does not cover backups, replicas, snapshots, or derived data unless the customer registered those as systems in their own right and they appear by name in the certificate.

It does not assert that recoverable data is gone. Several storage services retain deleted objects in a recoverable state — Google Cloud Storage soft delete, BigQuery time travel, database point-in-time restore. Where BurnLedger can detect such a state it refuses to certify rather than certifying around it, which is why some verifications fail that a customer expects to pass. Where it cannot detect one, absence from a live query is what was measured, and that is all.

It does not assert that BurnLedger never had write access. BurnLedger requires a read-only credential and refuses one it can prove is write-capable. For some datastores that proof is real: the engine is asked what privileges the credential holds. For others the engine exposes no way to ask, and the read-only status was asserted by the operator rather than verified.

Which of the two applies is on the record from format 7.0 onward. Every system entry in a 7.0 record carries read_only_enforcement, measured by the enclave at the moment it connected to run that system's query and covered by the signature, so a reader holding only the record can tell an introspected system from an asserted one. On a record issued as 3.0 through 6.0 that distinction is not on the record: it is held per system on the registration row and returned by the systems API, and a reader holding only such a record cannot tell the two apart. This document previously said each system entry said which, which was not true of any record then in existence; 7.0 is what makes it true, and the sentence is scoped to the format rather than stated flatly because records issued under the older formats are verified forever and none of them carries the field.

It does not assert compliance with any law. Whether a deletion satisfies GDPR Article 17, the CCPA, HIPAA or anything else is a legal question about the customer's obligations, their systems and their records. This document is evidence a customer may use in making that case. It is not the conclusion, and it is not legal advice.

It does not assert that BurnLedger deleted anything. The customer performs the deletion. BurnLedger observes before and after.

It does not assert anything about aggregates, embeddings or model weights. Every count in this document is a count of things a query returned: rows, documents, keys, objects. Data that was derived from a subject and then dissolved into something that is not a row — a total, a mean, an index or an inverted index built over the deleted text, a feature vector, the weights of a model trained on it — is not addressable by any query template, was not measured, and is outside the certificate. Registering the system that holds the derivative does not help: the certificate would still report what the customer's query returned, and a query cannot enumerate a subject's contribution to a sum or to a set of weights. Where a regime treats such a derivative as personal data, this document is silent on it.

A certificate does not expire, and nothing invalidates it if the data comes back. The signed format carries issued_at and no end date; there is no expiry field, and no verifier applies one. A certificate asserts what was observed at the two moments it names and is never re-checked. If the subject's data is re-ingested the next day — restored from a backup, resynchronised from an upstream system, collected again — the certificate does not change, does not lapse, and every verifier keeps returning the verdict it returned before. Revocation is the only thing that says otherwise, and it is an act rather than a detection: a human revokes the certificate with a stated reason and the revocation is published at GET /v1/certificates/{id}/status. Nothing in BurnLedger watches for re-ingestion, so nothing triggers that act. Keeping a certificate's assertion true over time is the customer's job, and re-attestation is how it is done.


3. What a third party can check without trusting us

This is the part worth relying on, and it is unusually strong, so it should be stated as precisely as the limits:

  • The certificate is signed with Ed25519. Anyone can verify that signature offline against the published key, with no network call and no account.
  • The signature is produced inside an AWS Nitro Enclave whose measurement is pinned in the KMS key policy: the key that signs is released only to that measured image.
  • Each certificate is entered in a public append-only transparency log, and the certificate carries an inclusion proof against a signed log head.
  • Revocation is published as a separately signed, short-lived statement at GET /v1/certificates/{id}/status, unauthenticated. A verifier given no fresh statement reports VALID_REVOCATION_UNKNOWN — never "valid" — because "not revoked" is not a property that can be established offline.

The transparency log contains certificate identifiers, hashes and timestamps. It contains no subject identifiers, no customer names and no data.

All of that is a property of the signed JSON, not of the PDF. The PDF is a rendering of the certificate for a human reader. It is produced by an ordinary PDF library, carries no PAdES or other embedded signature and no attachment, and nothing detects an edited PDF. It does print the certificate's Ed25519 signature as hex, but it prints neither the issuer's public key nor the canonical payload that signature was computed over, so the signature on the page cannot be checked against anything else on the page. The QR code resolves the record's revocation status and nothing more. A party who needs to verify rather than read needs the JSON.


4. The residuals we cannot close, stated plainly

The subject salt is supplied from outside the enclave. BurnLedger hashes a subject identifier with a per-customer salt before it is used. The salt is supplied to the enclave by the host system. A host that substituted a different salt would produce a certificate about a different subject hash, and no check inside the enclave can detect it, because the enclave holds no customer records.

This matters to exactly one question: whether the certificate is about the subject the customer intended. It cannot be used to change what was measured. Closing it means moving customer identity inside the enclave, which is on the roadmap and is not done.

A one-record Merkle root is a guessable commitment to that record. Under proof_mode: merkle, BurnLedger hashes each matching record's canonical form with SHA-256 and puts the Merkle root of those hashes in the signed certificate. The per-customer salt above applies to the subject identifier and to nothing else: record content is hashed unsalted. RFC 6962 makes the root of a one-leaf tree the leaf itself, so for a system where exactly one record matched, the published root is a fixed, keyless function of that one record's real content — SHA-256(0x00 || SHA-256(canonical record)) — and of nothing else.

Anyone holding the certificate can therefore test guesses against it offline, and the certificate tells them it is worth trying, because record_count: 1 sits beside the root in the same signed system entry. Where the record's fields range over a small or enumerable domain — a date of birth, a postcode, a diagnosis code, a boolean flag, a member number from a known range — guessing until the root matches recovers the content. This is the default path for the systems where it matters most: a system registered with phi_in_scope: true makes merkle the default proof mode for any attestation that includes it.

Closing it means keying record hashes, which changes every record hash and so requires a new canonicalization version. Canonicalization 3.0 is that version, and format 7.0 attests under it: each record's content hash becomes HMAC-SHA-256(record key, "burnledger.record_commitment.v3" || content hash) before it becomes a Merkle leaf, so the published root is no longer a keyless function of the record and guessing the record's content does not reproduce it.

Three things that fix does not do, stated as plainly as the defect:

  • It is not retrospective. A record already issued keeps the unkeyed root it was signed with, and re-signing historical records is precisely what ADR-017 forbids. For a 3.0–6.0 record with record_count: 1 and a merkle_root, the root remains a guessable commitment to that record forever.
  • It does not close the field-boundary ambiguity. Canonicalization 3.0 keeps 1.0's field encoding, so a binary-capable connector can still serialize two distinct records to identical bytes. 2.0 exists for that and still has no caller; it needs the version to reach the twenty connectors and come back from them, which is a separate piece of work.
  • It ends the one way the root could be checked. The key is drawn inside the enclave, once per attestation, and destroyed when that attestation is signed. Nothing stores it, returns it, or logs it, so from 7.0 the published root is a value no party — BurnLedger included — can open, and the route named below (hold the matching records, rebuild the tree, compare) is not available for a 7.0 record.

Storing the key would preserve that route, and is refused for the reason the next block refuses leaf disclosure: for a system where exactly one record matched, which is the ordinary case under phi_in_scope, the key beside the published root is a permanent, brute-forceable commitment to that deleted record's content, held by us. Drawing it inside the enclave rather than accepting it from the host is also what removes the substituted-key residual the subject salt still carries — a host cannot substitute a key it is never asked for.

Until 7.0 is issued, proof_mode: count publishes no record hashes at all and is the right choice wherever the count is the evidence needed, and merkle should not be used on a system whose individual records are low-entropy.

Subject identifiers move to a memory-hard function in the same version. Section 4's first residual is about a substituted salt; this is about a leaked one. Through format 6.0 a subject hash is SHA-256(salt || identifier), and SHA-256 is designed to be fast — so anyone holding the salt and a published hash can enumerate a small identifier domain, an email address or a member number, at billions of guesses a second. From 7.0 the function is Argon2id (64 MiB, three passes, four lanes), which cannot be sped up by better silicon the way SHA-256 can. Hashes already written keep the old function and remain enumerable by anyone who obtains the salt; that is why the salt is treated as a secret and not as a public parameter.

The obvious remedy is the wrong one, and this records why. The gap a reviewer notices first is that the root is unverifiable: unlike the transparency log, which has /v1/log/proof/inclusion and cmd/verifyleaves, there is no endpoint that will prove one record was in the tree, so the root's extra assertion is checkable only by someone who already holds every matching record and rebuilds the whole tree. Adding a leaf-disclosure or audit-path endpoint is not the fix, for three independent reasons, any one of which is sufficient:

  1. It would publish the very commitments the paragraph above is about. The root's protection for a many-record system is that testing a guess about one record requires knowing all of them. Per-leaf disclosure removes that: each leaf is separately SHA-256(0x00 || SHA-256(canonical record)), so the cost of recovering low-entropy content drops from the whole set to one record at a time, for every record in the system. The one-record residual would stop being a corner case and become the general case.
  2. An audit path is not a smaller disclosure. An RFC 6962 inclusion proof for leaf i discloses its sibling, which at the bottom of the tree is another record's leaf hash. Requesting proofs across the index range reassembles the full leaf set, so an audit-path endpoint reduces to leaf disclosure under enumeration rather than avoiding it.
  3. Serving either one would require retaining what this product exists to destroy. Leaf hashes are function locals inside the enclave and are discarded when the root is computed (enclave/local.go, queryMerkle); the data-classification policy records that they are never stored. They cannot be recomputed on demand either, because the records they describe were deleted -- that is the event the certificate attests. A prover would therefore need BurnLedger to keep a permanent, brute-forceable commitment to every deleted record, which inverts the guarantee the certificate makes.

The order is therefore fixed: salt record hashes under a new canonicalization version first, and only then is disclosing leaves a question worth reopening. A verifier who wants more than the root on a 3.0-6.0 record has the supported route -- hold the matching records, rebuild the tree, compare -- which discloses nothing to anyone who did not already have the data.

That route does not exist for a 7.0 record, and the trade is stated rather than glossed: 3.0 keys every leaf, the key is drawn inside the enclave and destroyed with the attestation, and a keyed root cannot be rebuilt by someone holding only the records. A 7.0 merkle_root is therefore a commitment no party can open. It still binds what the enclave measured -- the signature and the NSM attestation say which reviewed image computed it over what -- but it is not evidence a third party can test, and proof_mode: merkle should not be chosen on the belief that it can be. The alternative was to retain the key, and retaining it is the same act as retaining leaves: for a one-record system it is a permanent, brute-forceable commitment to the deleted record, which is what the three reasons above exist to prevent.

The query is bound to itself, not to the one the customer registered. Every system entry carries a query_hash, and the enclave checks it: it normalizes the query template it was handed, hashes it, and refuses the attestation if the result differs from the stated hash. Both values arrive in the same host-composed request, so that check compares one party's input against the same party's input. Registration lives on the host — the template and its hash are written to an ordinary database column by the API service, outside anything the enclave measures — so the enclave holds nothing of the customer's to compare against. A host that substituted the template and its hash together passes every check and obtains a validly signed, NSM-attested certificate naming its own question. A test asserts exactly that outcome by name — TestSignedQueryHashDescribesTheExecutedQueryNotTheRegisteredOne — so the gap is pinned rather than merely known, and cannot close silently.

Read query_hash accordingly. It binds the signature to the query that ran. It does not bind it to the query the customer wrote.

A second limit sits in the same field and holds even against an honest host: the hash covers the query's text, normalized for whitespace and keyword case and nothing else. It says nothing about what that text resolves to. A view redefined between attestation and verification, a column repurposed, a table swapped behind a synonym or an alias — each leaves the query text, and therefore the hash, identical, and nothing anywhere in the system detects it. "The same query, run twice" is a statement about the statement, not about the schema it was run against.

Both matter to the same question the salt residual does: whether the certificate is about what the customer thinks it is about. Neither can be used to change a count that was honestly measured. Closing the first means the registered template reaching the enclave by a path the host cannot rewrite, which is the same change — moving customer records inside the enclave — that closes the salt residual above, and it is not done. Closing the second means committing to schema identity alongside query text, which nothing does today and which no version of the format has been designed for.

Confirming that a record concerns a particular person is the Controller's act, and there is no other route. A record identifies its subject only by a salted hash. The only way to establish that a record concerns a named person is for the Controller — the customer who holds the identifier and whose salt was used — to re-hash that identifier and compare (DPA §7). BurnLedger operates no endpoint, support process or tool that takes an identifier, or a hash, and answers whether any record matches it, and it builds no such confirmation oracle. That is a stated refusal, not a technical impossibility: BurnLedger holds the salt (Privacy Policy §2) and could compute the hash, which is exactly why the refusal has to be written down. An oracle that answers "is there a record about this person" turns every record into a membership test against the deleted population for whoever can reach it, and would be reachable by parties the Controller never authorised. A third party holding a record who wants to know whom it concerns has one party to ask, and it is not us.


5. Proposed certificate text

Title. "Deletion Verification Record" rather than "Deletion Certificate"overruled 2026-08-27 (Determination 5): keep "Deletion Certificate". "Certificate" is not protected in general commercial use; the member-state concern attaches to regulated conformity assessment and to eIDAS qualified trust services, and BurnLedger holds itself out as neither. The published site carries no eIDAS, "qualified", "accredited" or trust-mark language, and "certified" does not appear in marketing copy at all. Renaming the product costs a great deal and buys almost nothing. Standing instruction: never add that language — it is where the actual regulatory line sits.

Status. VERIFIED rather than CERTIFIEDwithdrawn 2026-08-27: the premise was stale. The February brief recorded that the PDF renders Status: CERTIFIED. It does not, and has not since the v3 rework. pdf.go prints cert.Status, which is a CertificateStatus — the only values are ACTIVE and REVOKED.

CERTIFIED is an AttestationStatus: an internal lifecycle state meaning "this attestation has produced a certificate". It appears in the API, both SDKs and cmd/recert, and never on the artifact a counterparty reads.

So the concern the determination addressed — that the document's most prominent word asserts a legal conclusion the scope statement does not support — is not true of the document. Renaming the enum would be a breaking change across every published SDK in exchange for nothing a relying party can see. Not done.

What remains true, and is worth checking whenever the PDF changes: the most prominent word on the artifact must not assert more than this section allows. Today that word is ACTIVE, which asserts nothing legal.

Limitations block version 1.0, verbatim, on the PDF and in the signed JSON. This is the wording formats 4.0 through 6.0 are issued under. It is reproduced here unchanged and permanently: a record commits to the SHA-256 of the wording it was issued under, so a verifier holding only the newer text would report a mismatch on every genuine record issued under this one.

BurnLedger executed the query defined by the customer against the systems named above and observed the record counts shown. BurnLedger did not perform the deletion, did not audit whether the query identifies all copies of the subject's data, and does not cover systems not named here — including backups, replicas and snapshots unless separately registered. Recoverable and soft-deleted data may exist where the storage service does not expose it. This record is evidence of what was observed; it is not a legal opinion that any obligation has been satisfied. The signed JSON record, not this page, is what verifies; check it at .

Limitations block version 2.0, verbatim, on the PDF and in the signed JSON. This is the wording format 7.0 is issued under. It adds four things to 1.0 and narrows nothing: what one record is and that counts do not add across connector types (section 1.1); derived data in the list of what is not covered; that nothing is asserted about aggregates, embeddings or model weights; and that the record speaks about two moments and gives no assurance about any later one.

BurnLedger executed the query defined by the customer against the systems named above and observed the record counts shown. What one record is depends on the connector type and is defined by the record-unit table in Schedule A section 1.1; counts from different connector types are not comparable and do not add up. BurnLedger did not perform the deletion, did not audit whether the query identifies all copies of the subject's data, and does not cover systems not named here — including backups, replicas, snapshots and derived data unless separately registered. It says nothing about aggregates, embeddings or model weights, or about any other derivative no query template can enumerate. Recoverable and soft-deleted data may exist where the storage service does not expose it. This record states what was observed at the two moments it names; it does not expire, it is not re-checked, and it gives no assurance about any later moment — data present again afterwards does not invalidate it. This record is evidence of what was observed; it is not a legal opinion that any obligation has been satisfied. The signed JSON record, not this page, is what verifies; check it at .


6. Decisions this draft does not make

  1. The title and status wording above are engineering's recommendation, not a legal conclusion.
  2. Settled, and kept here as the record of it. Whether the limitations block belongs inside the signed payload. It does: text outside the signature can be stripped by whoever forwards the document. Certificate format 4.0, live since 2026-08-28, carries the scope version and a SHA-256 of the text above in the signed payload, so a verifier holding its own copy can prove which wording a record was issued under.
  3. What obligation arises on revocation, and whether relying parties who previously fetched a certificate must be notified.