File Uploads and Content-Addressed Artifacts

software-engineering
full-stack
files
http
storage
reliability
Carry browser file bytes through validated HTTP routes into retry-safe storage, then implement download, deletion, and restore.

Files force the stack to handle data that does not belong in JSON event rows. This chapter adds an Attach file control, raw-body upload and download routes, and a content-addressed local store for patches, screenshots, logs, and exports. The browser reports the resulting digest; the service validates size and type; the object store makes a retried upload resolve to the same identity.

Trace bytes separately from metadata

The browser reads the selected File into an ArrayBuffer and posts those bytes to /api/artifacts with its content type. FastAPI enforces a non-empty body and a 5 MiB local-course limit, then calls AutocodeApplication.put_artifact. The application delegates byte identity and atomic writes to LocalArtifactStore.

The response contains the SHA-256 digest, size, and content type. Session events should store that small reference rather than embedding bytes in SQLite or WebSocket frames.

from tempfile import TemporaryDirectory

from fastapi.testclient import TestClient

from autocode.runner import DemoAgentRunner
from autocode_service.api import create_app

with TemporaryDirectory() as directory:
    app = create_app(database_path=f"{directory}/sessions.db", runner=DemoAgentRunner())
    with TestClient(app) as client:
        first = client.post(
            "/api/artifacts",
            content=b"patch contents",
            headers={"Content-Type": "text/plain"},
        )
        retried = client.post(
            "/api/artifacts",
            content=b"patch contents",
            headers={"Content-Type": "text/plain"},
        )
        listing = client.get("/api/artifacts").json()
        downloaded = client.get(f"/api/artifacts/{first.json()['digest']}")

assert first.status_code == 201
assert first.json()["digest"] == retried.json()["digest"]
assert len(listing) == 1
assert downloaded.content == b"patch contents"
print("artifact digest:", first.json()["digest"])
artifact digest: ab3a636405ece190b645ffdbd50a2c46e2d01eff98192b5715242378abb97f05

The retry creates one visible object because the digest is both identity and idempotency key. The HTTP test covers browser-shaped bytes, request validation, application delegation, filesystem storage, listing, and download. In a larger deployment, metadata belongs in the database while bytes move to object storage through the same reference contract.

Choose proxy or direct transfer deliberately

The local service proxies bytes because it keeps the entire course runnable with one process. Object storage changes the efficient path: the API authenticates the request and mints a short-lived upload or download URL, while the browser transfers bytes directly to the object service. The session still stores the digest and metadata returned after verification.

Validate declared type, observed type when practical, length, digest, authorization, and filename display separately. Never trust a browser filename as a filesystem path, and never render uploaded HTML under the application’s trusted origin.

from tempfile import TemporaryDirectory

from fastapi.testclient import TestClient

from autocode.runner import DemoAgentRunner
from autocode_service.api import create_app

with TemporaryDirectory() as directory:
    app = create_app(database_path=f"{directory}/sessions.db", runner=DemoAgentRunner())
    with TestClient(app) as client:
        empty = client.post("/api/artifacts", content=b"")
        stored = client.post(
            "/api/artifacts",
            content=b"plain text",
            headers={"Content-Type": "text/plain"},
        )

assert empty.status_code == 422
assert stored.status_code == 201
assert stored.json()["content_type"] == "text/plain"
print("validation statuses:", empty.status_code, stored.status_code)
validation statuses: 422 201

The empty-body rejection happens before storage, while content type remains metadata rather than permission to execute bytes. The capstone should add browser upload evidence, retry count, stored object count, and failed download behavior to the release scorecard.

Exercises

Design a retention sweep for tombstones older than a grace period. List the evidence it must check before deleting bytes, and write the idempotency rule that makes running the sweep twice safe.

[P05.1] Design a retention sweep

State the checks required before physically deleting a tombstoned artifact and the property that makes the sweep safe to retry.

Back to top