Skip to content

feat(cloud): S3-compatible blob store for attestation durability #18

Description

@andyjsbell

Branch: feat/s3-blob-store
Depends on: current main
Priority: P1 — attestation evidence currently lives on one unreplicated disk.

Prompt:

The hosted service stores each attestation's canonical bytes (the actual audit evidence) as a content-addressed blob. Only two BlobStore backends exist: MemBlobStore (tests) and FsBlobStore (single local filesystem). In production on Fly.io the blobs sit on a single mounted volume with no replication or backup — for an audit-and-receipts product, losing that volume loses the evidence the whole product exists to preserve. This task adds an S3-compatible BlobStore (targeting Backblaze B2 / Cloudflare R2, already named as the intended backend in the code) so blobs are durable and the service can scale beyond one machine.

Context

  • services/awp-cloud/src/blob/mod.rs:22 — the BlobStore trait: put(sha256_hex, bytes), get(sha256_hex) -> Option<Vec<u8>>, and tamper_for_test (test-only). The module doc (blob/mod.rs:10) explicitly names "An S3-compatible backend (Backblaze B2 / Cloudflare R2) is the production" target.
  • services/awp-cloud/src/blob/filesystem.rsFsBlobStore, the reference production-ish impl (path = key). Match its idempotency semantics: re-putting the same key with identical bytes is a no-op; mismatched bytes surface as Internal.
  • services/awp-cloud/src/blob/memory.rsMemBlobStore, the test double.
  • services/awp-cloud/src/lib.rs:14-15 and state.rs:5 — comments note "an S3 impl arrives with the staging deploy" and that the production binary builds a "Postgres + filesystem (or S3)" state.
  • services/awp-cloud/docker-compose.yml — already runs Minio "for parity… when the S3 blob impl lands"; use it as the local S3 target for tests.
  • services/awp-cloud/Cargo.toml — already depends on reqwest (rustls-tls) and sha2; prefer a lightweight S3 client. If an SDK is needed, aws-sdk-s3 works against any S3-compatible endpoint via a custom endpoint URL — ask before adding it if a hand-rolled signer is preferred, per the repo's "ask before adding deps" rule.

Your Task

  1. Add services/awp-cloud/src/blob/s3.rsS3BlobStore implementing BlobStore. Config from env: BLOB_S3_ENDPOINT, BLOB_S3_BUCKET, BLOB_S3_REGION, BLOB_S3_ACCESS_KEY_ID, BLOB_S3_SECRET_ACCESS_KEY. put maps sha256_hex to an object key, uploads with idempotent semantics (a HEAD/exists check to preserve the no-op-on-identical-bytes contract), get fetches or returns None on 404. Implement tamper_for_test with #[cfg(test)] or an equivalent gate so it cannot be called in production builds.
  2. Register the moduleservices/awp-cloud/src/blob/mod.rs: add pub mod s3;.
  3. Select the backend at bootservices/awp-cloud/src/main.rs / state.rs: choose S3BlobStore when BLOB_S3_BUCKET (or a BLOB_BACKEND=s3 switch) is set, else fall back to FsBlobStore. Keep FsBlobStore as the default for local dev.
  4. Update deploy configservices/awp-cloud/fly.toml: document/set the S3 env vars so production uses the durable backend rather than the mounted volume (leave the volume for fallback but stop treating it as the source of truth).

Do Not Touch

  • src/blob/filesystem.rs and src/blob/memory.rs — leave the existing backends intact; they remain the dev/test paths.
  • The BlobStore trait signature in blob/mod.rs — implement against it; do not change the trait.
  • Ingest/read verification logic in src/handlers/attestations.rs — it calls BlobStore through the trait and needs no change.

Verification

make check
# → lint and tests pass

# Integration test (add services/awp-cloud/tests/s3_blob.rs, gated on env):
cd services/awp-cloud && docker compose up -d minio
export BLOB_S3_ENDPOINT=http://localhost:9000 BLOB_S3_BUCKET=awp-test \
       BLOB_S3_REGION=us-east-1 BLOB_S3_ACCESS_KEY_ID=minio BLOB_S3_SECRET_ACCESS_KEY=minio123
cargo test --test s3_blob
# → put/get round-trips a blob; get of an unknown key returns None; re-put of
#   identical bytes is a no-op

# Confirm the module is registered:
grep -n "pub mod s3" services/awp-cloud/src/blob/mod.rs
# → present

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions