Tamper-evident decision records for AI, anchored to RFC 3161
Article 12 of the EU AI Act was supposed to bite on 2 August 2026. The Digital Omnibus (Regulation (EU) 2026/1744, in force 27 July 2026) moved it to 2 December 2027. So there are sixteen months left, which is roughly ho
Article 12 of the EU AI Act was supposed to bite on 2 August 2026. The Digital Omnibus (Regulation (EU) 2026/1744, in force 27 July 2026) moved it to 2 December 2027. So there are sixteen months left, which is roughly how long it takes to find out your logs are not admissible as evidence. What lands then is automatic event logging that makes a high-risk system's decisions traceable, six months minimum retention, longer under some Annex III regimes.
I went looking for a tool and found that every LLM observability platform records the same thing: that the model retrieved document X. None of them record what X said at the time. That gap matters because sources get revised. I measured a commercial fundamentals feed over four months: across 2,163 earnings records, 41.4% of epsActual values differed between first-seen and final, and 15.3% of the 2,163 differed enough to flip a long-entry decision. A second, better-designed capture over a broader universe gives 18.6% and 4.6%. Both record sets are published and neither is hidden behind the other. A trace saying "we retrieved X" can't tell a good decision from a lucky one.
So tg-attest records the point-in-time state of every piece of evidence a decision consumed, hashes each record into a chain, batches them into Merkle epochs, and anchors the epoch root to an RFC 3161 timestamp authority.
Three things I'd point at:
The Merkle layer isn't gold plating. A regulator asks about one loan. Without inclusion proofs you hand over the whole ledger to answer. With them you hand over 8 KB.
The external anchor is the part people skip. A hash chain you control proves nothing against you, because you can rewrite all of it and recompute. Only a third-party signature over the epoch root makes "these records existed before time T" an assertion you can't fake. Costs pennies per day. You don't need to anchor every epoch, since anchoring one bounds every epoch before it.
An auditor verifies with a CA certificate they fetch themselves, not one I supply. The timestamp checks out with stock openssl ts and nothing installed. The Merkle proof needs either this library or about forty lines written from the spec in the bundle.
Verify it yourself, in about 30 seconds
A real disclosure bundle is committed to the repo. Without installing anything โ openssl and nothing else:
$ git clone https://github.com/lizhuojunx86/tg-attest && cd tg-attest/examples/verify-me
$ openssl ts -verify \
-digest 6127a62bc10984770572d9c574b8bdc5a5f52d373127ad2fd526992b0abcddd6 \
-in epoch_000.tsr -token_in -CAfile freetsa_ca.pem
Verification: OK
That digest is the epoch_hash from decision_0000.json, and openssl has just confirmed FreeTSA signed it. No part of that command trusts my library. The full ten checks, with the library:
$ pip install tg-attest[tsa]
$ python -m tg_attest.cli decision_0000.json --ca freetsa_ca.pem
ๅณ็ญ seq=0 ๅณ็ญๆถ้ด 2026-08-04T23:35:17.003+00:00
ๆง่ก่
alpha-v2/pead ๆจกๅ claude-opus-5
่ฏๆฎ 1 ๆก๏ผ้ธ้จ 1 ้
้่ฟ
โ ่ฎฐๅฝๅ
ๅฎนๅๅธ่ชๆดฝ
โ ่ฎฐๅฝๆปก่ถณๆๅฃฐๆ็ๅฎๆดๆงๆกฃๆก
โ Merkle ๅ
ๅซ่ฏๆๆๆ
โ ๆถ้ดๆณ/eContentType ไธบ id-ct-TSTInfo
โ ๆถ้ดๆณ/messageImprint ๅน้
epoch_hash
โ ๆถ้ดๆณ/EKU ไป
ๅซ timeStamping
โ ๆถ้ดๆณ/EKU ๆฉๅฑไธบ critical
โ ๆถ้ดๆณ/signedAttrs.message-digest ๅน้
ๅ
ๅฎน
โ ๆถ้ดๆณ/TSA ็ญพๅๆๆ
โ ๆถ้ดๆณ/่ฏไนฆ้พ่ณๅฏไฟกๆ น
TSA ็ญพๅๆถ้ด๏ผ2026-08-04T23:35:17+00:00
็ป่ฎบ๏ผ่ฏฅ่ฎฐๅฝๅจ 2026-08-04T23:35:17+00:00 ไนๅๅณไปฅๆญคๅฝขๆๅญๅจใ
Yes, the CLI answers in Chinese โ I built it for my own production use first. --json gives the same ten checks machine-readable, and the README maps every line. To watch it fail, flip one character of output_hash in the JSON and run it again: exit code 1, and the first check goes โ.
The documents I'd actually read
The ones about where this library was wrong. A fail-open audit of every except and default branch in the package, six of which were looser than they looked. Mutation testing that found four chain-validation branches no test reached, all of them accepting evidence they should have rejected. A claim-by-claim evidence table where five README claims got rewritten weaker. And a threat model whose second half is longer than its first.
Caveats, because they're load-bearing. The default TSAs are not eIDAS qualified, which means Article 41(1) admissibility but not 41(2)'s legal presumption. Use a QTSP from the EU Trusted List if you'll ever be in a dispute, and record its qualified status at stamping time rather than at verification time. Qualification gets withdrawn. Also, this doesn't parse model output into claims, and won't. Claim extraction needs an LLM, and an audit tool that leans on an unverifiable component isn't an audit tool.
v0.1, Apache-2.0, API will move. pip install tg-attest[tsa]. tg-attest is the evidence layer under TraceGuard, where both revision record sets live. If you're working through Article 12 and I got something wrong, I'd rather hear it now.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes โ full credit and traffic to the original publisher.