5bb4b7e8

By: Michael Lynch <git@mtlynch.io>

Move media production behind consumer-owned stores

The former media API exposed provider mechanics to upload and import code: callers supplied seekable readers and MIME values, and a shared WriteConverted helper staged output for adapters. This made business code understand requirements that only local or S3 storage should own and left the S3 wire behavior untested.

Litestream v0.5.14 in files replica_client.go and s3/replica_client.go keeps backend-specific replayability, buffering, headers, and upload mechanics inside the adapter. Its shared replica tests then assert observable behavior across implementations rather than coupling algorithms to provider internals. Adopt the narrow part of that pattern needed here instead of copying Litestream's larger read, list, delete, multipart, retry, or telemetry surface.

Replace WriteFile, ContentType, and WriteConverted with consumer-owned Write(filename, produce) contracts in uploads and importers. Conversion code supplies a producer callback; LocalWriter decides how to open the destination, while the S3 writer buffers replayable bytes, derives MIME type from the generated filename, and applies its cache policy. Migrate all callers and test fakes, and add local conformance coverage plus httptest-based assertions for the actual S3 method, key, payload, headers, producer failure, and remote error.

The main implementation constraint was the repository rule against filesystem writes in tests. LocalWriter therefore accepts an internal file-creation seam so its path mapping and producer behavior can be tested in memory. S3 buffering is intentionally adapter-owned and trades memory for a replayable AWS SDK body; this branch deliberately does not claim atomic local writes, cleanup, context propagation, or recovery because none is part of the minimal contract.

No external blockers were encountered. Focused tests, the full Go checks, and every flake target passed before the amendment.

Suite timing

Time to Start Worker time Duration Time to finish Idle
Config 27s 17s 17s 45s 27s
Eval 52s 1m39s 1m39s 2m31s 6s
Build 2m05s 2m24s - - 1m31s
Suite 27s 4m21s - - 2m06s

Timeline

0s40s1m1m20s1m40s2m2m20s4m20s4m40s5m