Technical Report 001 · HashVault
Content-Addressable Storage
Storing the same bytes once, and deleting them exactly when nobody needs them.
Issue 01 · July 2026 · Filed under: Storage · Distributed Systems
§1 Address by content, not by name
In HashVault a file is not its bytes. A file is a row that points at a storage object, and a storage object is keyed by the SHA-256 of its contents: objects/{checksum}. Two users uploading the same PDF produce two file rows and one object.
Because the key is derived from the content, re-uploading identical bytes always resolves to the same key. There is nothing to look up by filename and nothing to reconcile.
§2 The upload path
The client hashes the file and asks to upload. If the checksum already exists, the API answers deduplicated: true and skips issuing a presigned URL altogether — the upload round trip disappears.
Otherwise the client receives a presigned PUT, sends bytes straight to S3, and confirms. The Go server only ever sees metadata. It lives on the control plane.
§3 Reference counting is the hard part
Each object carries a ref count. Confirming an upload increments it; deleting a file decrements it; the object is purged from S3 and Postgres only when the count reaches zero. Both operations are atomic SQL, never read-modify-write in Go.
The failure that matters is concurrency: fifty clients confirming the same checksum at the same moment must yield exactly one object with a count of fifty. That is a property of the database, so it is tested against a real Postgres rather than a mock.
§4 Quota without surprises
Quota is checked at init and again at confirm. A deduplicated file still counts against the uploader's quota — storage is shared underneath, but accounting is per user.