If you are searching for an immutable audit log, the hard part is probably not choosing a hash function. It is deciding what your system must make impossible, what it merely makes detectable, and which party the reader still has to trust.
A normal application database is designed around current state. A row changes when an address, status, or calculation changes. That is useful for operations, but weak evidence: the database shows what the system says now, not what it said at an earlier point.
A published history needs a different model. The original observation must remain visible even when it was wrong. Corrections become new events rather than replacements.
Model events, not mutable facts
A minimal event might contain:
sequence: 1842 kind: "daily_observation" observed_at: "2026-08-31T23:59:59Z" published_at: "2026-09-01T00:04:12Z" payload: { ... } correction_of: null previous_hash: "8e5c..." hash: "33a1..." The hash should cover a canonical encoding of every meaningful field, including previous_hash. Canonical encoding matters because semantically identical JSON can have different byte representations. Decide how keys are ordered, numbers are represented, Unicode is normalized, and absent fields are handled before hashing.
Linking each event to the preceding hash creates a hash chain. Editing an old event changes its hash and invalidates every later link. A Merkle tree is another option when readers need efficient proofs for individual records without downloading the full log.
Neither structure prevents an operator from rebuilding the whole history. Detection requires a checkpoint that the operator cannot quietly replace alongside the data. Examples include:
- A signed head hash copied to independently controlled storage.
- Periodic publication through a separate service or repository.
- Write-once object storage with a retention policy.
- A commitment recorded on a public chain.
- Checkpoints sent to multiple external observers.
These mechanisms have different costs. Public chains provide visible ordering and durable commitments, but add fees, latency, and privacy concerns. Write-once storage is simpler but leaves readers dependent on the storage account and its configuration. Signed releases are easy to inspect, though a signing-key compromise can undermine later checkpoints.
Timestamps need a trust model
A published_at column written by your own application is a claim made by that application. It does not independently establish when publication happened.
An external timestamp or public commitment provides stronger evidence that some bytes existed no later than a certain point. It still does not establish when the underlying event occurred. Keep observed_at, ingested_at, and published_at separate so readers can see delays rather than receiving one ambiguous timestamp.
Sequence numbers also expose gaps. If a log moves from sequence 1842 to 1844, readers should be able to ask where 1843 went. Random identifiers are useful for uniqueness, but they make omission harder to notice.
Corrections are events
Suppose a data source supplied the wrong closing price. Updating the original row destroys evidence of both the mistake and its timing. Append a correction instead:
sequence: 1843 kind: "correction" correction_of: 1842 reason: "Source published a revised closing price" replacement: { ... } previous_hash: "33a1..." hash: "ad72..." Consumers can then derive the current view by applying correction events, while auditors can reconstruct what was visible at each checkpoint.
This complicates queries. Every consumer needs rules for superseded records, duplicate corrections, and corrections to corrections. Materialized views can provide convenient current state, but they should be treated as disposable projections of the event log rather than the evidence itself.
Database permissions help enforce the model. The application writer can receive INSERT access without UPDATE or DELETE, while a separate process publishes checkpoints. That separation raises the cost of tampering. It does not make administrators powerless: someone with infrastructure control may alter permissions, replace services, or restore a modified snapshot. Operational alerts and independently held checkpoints still matter.
What append-only storage cannot establish
An intact row can still be false.
The source may have sent bad data. A parser may have used the wrong unit. An operator may have omitted an inconvenient event before committing the next checkpoint. A clock may have been misconfigured. The log may accurately preserve a calculation whose assumptions were flawed.
Append-only design supports integrity after publication. It does not establish source accuracy, completeness, identity, or sound methodology by itself.
This distinction is often blurred. A statement such as “the history is immutable” says little unless readers know what was committed, when it was committed, who controlled the writer, and where checkpoints can be independently verified.
What a reader should inspect
Before relying on a published history, check:
- Start date: Does the publisher state when tracking began, rather than implying coverage of an earlier period?
- Publication delay: How much time passed between observation and commitment?
- Source data: Are sources, units, time zones, and transformation rules documented?
- Correction policy: Are corrections appended with reasons, or can old values disappear?
- Checkpoint location: Can a reader obtain a checkpoint from somewhere outside the primary application?
- Completeness: Are sequence gaps, missing days, and excluded records explained?
- Reproducibility: Can another developer rebuild derived values from the disclosed inputs and rules?
- Key handling: Who signs checkpoints, and what happens after suspected key compromise?
- Scope: Does the commitment cover raw inputs, derived output, metadata, or merely a rendered page?
A screenshot or chart is presentation, not an audit trail. An API response is stronger for automation, but still mutable unless tied to preserved commitments.
Disclosure: I build Tradevo, software for building, historical-testing, paper-trading, reviewing, publishing, subscribing to, and automatically running crypto strategies from user-controlled wallets; once tracking starts, a strategy’s daily forward record is written against real prices and is not edited.
The design test
Ask what happens when a published record becomes inconvenient. If an operator can overwrite it through the ordinary administration path, the history is a feature of policy rather than structure.
A credible append-only system preserves the original event, appends the correction, links both to verifiable checkpoints, and documents what those checkpoints do not prove. The inconvenience is intentional. That friction is what gives the record evidentiary value.


