ADR 0011: Content-addressed asset store with DB metadata¶
Status¶
Accepted — 2026-09-19
Context¶
Every iteration produces up to two images (raw provider output, final after fit and overlay), plus reference images and previews. Batch runs reuse the hero's asset as a reference across many iterations, resumed runs must not duplicate files, and the idempotency key (ADR 0012) needs a stable identity for "the reference image". Files must survive relocation of the data directory and be verifiable.
Decision¶
- Files live under
<data_dir>/assets/<sha256[:2]>/<sha256>.<ext>. Theassettable storesrel_pathrelative todata_dir, so the data directory is relocatable. assetcolumns:id,sha256 UNIQUE,rel_path,mime,width,height,bytes,kind CHECK IN ('raw','final','reference','preview'),compliant BOOL NULL,compliance_report_json NULL, plus the standard timestamps.- Write path: write to
<data_dir>/tmp/<ulid>,fsync, compute sha256,renameinto place. Identical bytes dedupe to the same row (putreturns the existingAsset). -
AssetStoreAPI (storage/assets.py):class AssetStore: def put(self, src: bytes | Path, kind: AssetKind) -> Asset def path_for(self, asset: Asset) -> Path def verify(self, asset: Asset) -> bool # re-hashes; False on drift -
Assets are immutable; "editing" produces a new asset. Deletion happens only via
runs delete --assets, which unlinks files no longer referenced by anyiterationorrun.reference_asset_id. - The hero→batch link stores
run.reference_asset_id; providers receivepath_for(asset)as an absolute path inGenerationRequest.reference_images(PLAN.md§3.1).
Consequences¶
- Re-running a step that yields identical bytes costs no disk space and no new row.
sha256of the reference asset is a stable input to the idempotency key.thumb exportis a copy frompath_for; nothing in the store is ever served in place.- Orphaned files after a crash between
fsyncand the DB insert are cleaned bydb vacuum, which also removes staletmp/entries.
Alternatives considered¶
- Store image bytes as BLOBs in SQLite — rejected: multi-megabyte rows bloat the database, slow
VACUUM, and prevent opening the files with normal viewers. - Path-addressed files (
runs/<run>/<ordinal>.jpg) — rejected: duplicates identical output, and a moved or renamed file silently breaks the reference link; hashing gives verifiability. - Git LFS or an object store — rejected: this is a local single-user tool; no remote is involved.