feat(storage): implement content-addressable PathResolver (S06 Step 5)
What
Implements the concrete PathResolver — a pure, I/O-free function family that computes ADR-008's content-addressable storage path layout from a namespace UUID and a content hash. This is Step 5 of the Storage Layer (S06) plan, a Stage 2 / Track A prerequisite for the real PostgreSQL BlobStore/Session.
-
NewPathResolver(rootDirectory string) PathResolver— public factory returning thePathResolverinterface introduced in Step 1. -
DefaultRootDirectory("artifact_registry") — the documented default path prefix. -
ObjectPath,UploadPath,NamespacePathproduce:{root}/{ns_hash[0:2]}/{ns_hash[2:4]}/{ns_hash}/objects/{ch[0:2]}/{ch[2:4]}/{ch} {root}/{ns_hash[0:2]}/{ns_hash[2:4]}/{ns_hash}/uploads/{upload_id}where
ns_hash = hex(SHA256(namespace_id UUID bytes))andch = hex(SHA256(content)). The{root}/.../{ns_hash}prefix is shared byobjects/anduploads/, so the commit-time move is always intra-partition.
Why this shape
- Content-addressable keying per ADR-008: the content hash is the object key directly.
- Two-level sharding (65,536 buckets per level) on both
ns_hashandcontent_hashto distribute writes. - Reversible parseability per ADR-011: given a path,
ns_hashandcontent_hashare recoverable by component parsing with no database lookup, enabling reconciliation.
References
- Plan:
docs/plans/2026-05-15-storage-layer.md— Step 5 (PathResolver implementation and tests). Plan MR merged onmain. - Spec:
docs/specs/S06-storage-layer.md— PathResolver section + acceptance criteria. - ADRs: ADR-008 (content-addressable storage), ADR-011 (reversible path structure).
Tests
internal/storage/paths_test.go (unit, runs in go_unittests):
- Golden-string assertions of the literal ADR-008 layout for all three methods (known UUID/content fixtures, incl. zero-UUID and empty-content).
rapidproperty tests: reversibility (object + upload), determinism, namespace-path-is-prefix, and namespace isolation (distinct namespaces never share a prefix).- Sharding correctness and the
root_directoryoverride.
Acceptance
-
go build ./...,go vet,golangci-lint(0 issues),goimports,go test -race -short ./internal/storage/all pass locally.