Algostream_infrastructure_persistence.Audit_logAppend-only, hash-chained audit log.
Event_logEvent_log already frames records with a length and a CRC32 and reads back independently, so reusing it looks obvious. Four things rule it out, each on its own sufficient:
Event_log.Writer.create opens with O_TRUNC, so a daemon restart would empty the audit trail. Changing that flag would change semantics for every existing caller, including the deterministic replay fixtures.Event.t has no actor. Adding one is not the safe kind of bin_prot change: appending a variant constructor preserves compatibility, altering the record does not, and every v2/v3 fixture would need regenerating.fsync per control action; the event firehose runs at tens of thousands of events a second and deliberately does not.Event_log.Reader.iter stops silently at the first bad frame, which is exactly backwards here: silent truncation is the tamperer's preferred outcome, so this reader reports the break instead.The framing shape is borrowed on purpose, so the two files read as siblings.
hash_0 = SHA-256("algostream-audit-v1\x00" || prev_file_hash)
hash_n = SHA-256(hash_(n-1) || canonical_n)On disk each frame is u32 length | canonical | 32-byte hash. The previous hash is not stored alongside — it is the previous frame's hash, and writing it twice would create a second thing that can disagree with the first.
Files rotate daily as audit-YYYYMMDD.log. Each header carries the previous file's final hash and name, so the chain survives rotation and Verify.directory can walk a whole directory.
Read on its own, verify catches a modified record, a deleted interior record and a spliced-in frame — each shows up as a chain mismatch at the first record the attacker did not also rewrite.
But the chain is unkeyed, so anyone who can write the file can recompute every hash from the point they changed onward and hand you a file that verifies perfectly. Two consequences follow, and both are real:
So the chain's actual guarantee is narrower than "the file is tamper-proof", and stating it plainly matters more than the property sounding strong: any change to the log moves the head hash. That is what makes it evidence.
Which is why the out-of-band anchor is not optional. algostream-auditctl head prints the current sequence number and head hash; record it somewhere the daemon — and therefore anyone who compromises the daemon — cannot write. A later verify compared against that anchor closes every gap above. Without an anchor this is a corruption check with good manners, not tamper-evidence.
HMAC-chaining with a secret was considered and rejected: the key would have to live on the machine the attacker owns, so it moves the problem rather than solving it.
This log is not certified against any regulatory regime. It is a defensible record of control actions, not a compliance claim.
module Writer : sig ... endAppend-only writer. One per directory; not thread-safe, so the daemon owns exactly one and calls it from the single dispatcher fiber.
module Reader : sig ... endmodule Verify : sig ... end