DEV Community

João André Gomes Marques
João André Gomes Marques

Posted on • Originally published at asqav.hashnode.dev

Per-agent hash chain, with a verifier that re-derives

We hash-chain every signature record so a tamper or a delete is detectable on a later integrity check. The chain has been live for months. It worked. It also had two real problems we did not surface until we did a cold audit against an older trust-data-infrastructure (TDI) project.

Problem 1: the chain was global

Every new SignatureRecord took its previous_hash from the most recent record across the entire database. Not the most recent record for the same agent. Not the most recent record for the same organization. The most recent record, period.

That makes a single global chain across every tenant. If tenant A's record is mutated or removed, the chain breaks at the next record from any tenant. A long-running customer with an active integration could have their chain show as broken because a different customer in a different country had a row deleted by a cleanup job.

It also has a write hot spot. Two parallel signs across any two tenants both read MAX(id), both compute the same previous_hash, both insert. The integrity check then flags the second insert as broken even though no one tampered with anything.

Problem 2: the verifier never re-derived

The verifier walked records in order and checked that each row's previous_hash matched the prior row's stored record_hash. That catches a deleted row. It does not catch a row mutation that also rewrites its own record_hash.

The fix is to re-derive record_hash from the canonical fields and compare. If the recomputed hash differs from the stored one, that row was mutated.

What we shipped

  1. Scope chain heads per-agent.
  2. Re-derive record_hash in the verifier.
  3. Tag legacy cross-agent links as legacy_global rather than treat them as broken.

No backfill in this PR. Old records keep their global-chain values; new records use per-agent from this commit forward.

PR with the diff and tests: github.com/jagmarques/asqav#138

If you run a similar chain anywhere (audit log, ledger, payments), the two checks above are worth a 30-minute look. Global ordering and stored-hash comparison both feel safe and both have failure modes that quietly do not surface until someone audits them.

Top comments (0)