Skip to content

Repository files navigation

Qavelys

I did not build a prompt-to-image app, a media dashboard, or a file uploader. I built a storage origin where a missing asset key generates itself once, proves how it was made, and serves from Backblaze B2 forever after.

Open Qavelys · Watch the 3-minute demo · Inspect a verified run · Read the docs

Watch the Qavelys demo

The story

Apps reference media by key. product/hero.png either exists or the page breaks, and filling the gap means the same manual loop every time: generate an image somewhere, download it, upload it to storage, wire the URL, hope nobody can tell where it came from.

Existing tools each own one step. A generator makes a file. A bucket stores a file. A CDN caches a file. None of them treat a missing key as the trigger and none of them keep a checkable record of how the object was made.

No dramatic receipt here, the proof is a live one. Run 9f7a1fb0-03c5-4e37-9976-125545cc83a4 on the deployed service generated a 1328x800 PNG on first request, stored the winner, the reject, the sources, the manifest, and a receipt in B2, then returned a byte-identical hit on the second request with SHA-256 ac29e96a117f3b458b3bfdee7968c0e5cd1b169004b87b3a10a36576bd35a9be.

Try it in 2 minutes

No login is needed for any read-only proof route.

Web path:

  1. Open the verified run report.
  2. Read the B2 object paths, provider and model, quality decision, and the two hashes.
  3. Open the Origin Studio and watch a request move through MISS -> GENERATE -> VALIDATE -> STORE -> HIT.

Curl path (all read-only, none of these spend generation quota):

BASE=https://qavelys.onrender.com
RUN=9f7a1fb0-03c5-4e37-9976-125545cc83a4

curl -s $BASE/health
curl -s $BASE/api/runs?limit=5          # recent receipt summaries
curl -s $BASE/api/runs/$RUN             # the canonical receipt
curl -s $BASE/api/verify/$RUN           # recomputes every artifact hash

Generation itself is fail-closed behind a server-side bearer code, so the public path is verify-only by design. The report and verify routes let a judge confirm the whole run without any credential.

How it works

A read is free. A miss is where the work and the rules live. The object that gets served on the second request is the same object the receipt describes, and every transition writes durable evidence to B2.

Operation Result
GET /api/assets/{key} Returns the stored B2 object, never generates
GET /assets/{key} on a hit Serves the stored object immediately
GET /assets/{key} on a miss Authenticate, apply quotas, then generate once
Generation without a bearer code Rejected before any storage or provider work
Repeat request for the same key Normal B2 hit, no new generation
Tampered stored object or receipt /api/verify fails the matching hash check

Ways I tried to break it

Every row maps to a test in backend/tests/ that runs under the verification command below.

Case Outcome Proof
Tamper with the final object after a good run /api/verify fails the final hash check test_run_receipt_verifies_then_detects_tampered_final_object
Tamper with the receipt JSON Verification fails test_verification_detects_tampered_receipt
Tamper with the manifest or a reject Verification fails test_verification_detects_tampered_manifest_and_reject
Fire two concurrent requests for the same missing key One generation, one hit test_concurrent_duplicate_requests_generate_only_once
Flood generation misses 429 with Retry-After, hits still served test_generation_rate_limit_returns_429_without_blocking_hits
Request generation without the bearer code Fails closed, reads stay public test_required_generation_auth_fails_closed_without_token
Request an unsafe asset key 400 before any external call test_bad_key_returns_400
Force a provider failure Secret-safe error receipt, no leaked provider text test_provider_failure_writes_secret_safe_error_receipt

Live proof

Item Value
Live service https://qavelys.onrender.com
Verified run report /reports/9f7a1fb0-...
Prompt refinement Groq llama-3.3-70b-versatile
Image generation NVIDIA NIM black-forest-labs/flux.2-klein-4b through Genblaze
Final asset 1328x800 PNG, 473887 bytes
Final SHA-256 ac29e96a117f3b458b3bfdee7968c0e5cd1b169004b87b3a10a36576bd35a9be
Integrity checks 9/9 pass, see Production Validation

Product screens

Qavelys landing page

Qavelys Origin Studio showing a B2 cache hit

Qavelys receipt verification

Mobile captures are in docs/screenshots.

How this differs

Alternative What it does Why Qavelys is different
Backblaze B2 upload / any bucket Stores a file you already made The missing key triggers generation, and B2 holds the provenance, not just bytes
Prompt-to-image apps Produce a file you then place yourself Addressed by asset key and served like a normal origin on the second request
Caching CDN or image proxy Caches objects that already exist Creates the object that did not exist, once, with a verifiable receipt

What's real

The shipped path is real and there are no mocked values in this repository. The verification is deterministic code that recomputes and compares hashes. Groq and NVIDIA are real external calls with graceful failure receipts, not a model pretending to decide truth. One controlled production run is recorded end to end in Production Validation.

Pending: a recorded demo video.

# 53 backend and CLI tests, no network needed
PYTHONPATH=backend PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 \
  .venv/bin/python -m pytest -q

# 26 desktop and mobile browser tests
npm --prefix frontend run test:e2e

# Full release QA
./bin/qavelys qa full --timeout 300

Honest limitations

  • Qavelys is an HTTP origin, not a complete signed S3 proxy.
  • Quality checks are deterministic media checks (format, dimensions, size), not subjective image scoring.
  • Rate counters and active-key locks are process-local, so multiple workers need shared coordination.
  • The code is unaudited hackathon-stage software.
  • Public receipts can carry prompt provenance, so confidential prompts must not be sent to a public deployment.
  • The live service runs on a free Render instance and can cold start.

B2 object layout and receipt

B2 is the system of record. Qavelys needs no application database for completed runs because the receipt and its related objects stay in B2.

sources/{run_id}/...        original and refined prompt, matched rule
final/{key}                 promoted asset
rejects/{run_id}/...        non-winning candidates with reasons
receipts/{run_id}.json      canonical receipt
receipts/by-key/{key}.json  pointer to the current receipt
genblaze/runs/.../manifest.json   generation manifest

Receipt shape (real keys, from backend/app/origin.py):

{
  "schema_version": "1.0",
  "run_id": "9f7a1fb0-03c5-4e37-9976-125545cc83a4",
  "asset_key": "demo/...",
  "status": "completed",
  "rule": { "id": "...", "sha256": "...", "prompt": "..." },
  "genblaze": { "provider": "nvidia-image", "model": "black-forest-labs/flux.2-klein-4b",
                "manifest_key": "...", "manifest_sha256": "..." },
  "prompt_refinement": { "provider": "groq", "status": "..." },
  "b2": { "final_key": "final/...", "rejects": [], "sources": [], "receipt_key": "receipts/..." },
  "quality_gate": { "...": "..." },
  "hashes": { "asset_sha256": "ac29e96a...bd35a9be" }
}

Architecture

flowchart LR
    A[Asset request] --> B{Final object in B2?}
    B -->|HIT| C[Serve stored media]
    B -->|MISS| D[Authenticate and apply quotas]
    D --> E[Match rules.json]
    E --> F[Refine prompt with Groq]
    F --> G[Generate candidates through Genblaze]
    G --> H[Validate and select]
    H --> I[Write sources, reject, manifest, final, receipt]
    I --> J[Verify hashes]
    J --> C
Loading

See Architecture for object layout, trust boundaries, receipts, and request sequencing.

Run locally

git clone https://github.com/mystiquemide/qavelys.git
cd qavelys
python3.12 -m venv .venv && .venv/bin/python -m pip install -r backend/requirements-dev.txt
cp .env.example .env.local   # fill B2, NVIDIA, and Groq values
.venv/bin/python -m uvicorn app.main:app --app-dir backend --host 127.0.0.1 --port 8000

Frontend in another terminal:

cd frontend && npm ci && npm run dev   # http://127.0.0.1:5173

Environment

Variable Required Purpose
BACKBLAZE_B2_ENDPOINT Yes B2 S3-compatible endpoint
BACKBLAZE_B2_KEY_ID Yes Server-side B2 application key ID
BACKBLAZE_B2_APPLICATION_KEY Yes Server-side B2 application key
BACKBLAZE_B2_BUCKET Yes Private B2 bucket
NVIDIA_API_KEY Yes Image generation provider access
GROQ_API_KEY Yes Prompt refinement
ORIGIN_REQUIRE_GENERATION_AUTH Production Enables fail-closed generation auth
ORIGIN_GENERATION_TOKEN Production Bearer access code for generation misses

All settings and safe defaults are in .env.example and the configuration reference. Never commit .env.local.

API

Method Route Purpose
GET /health Minimal readiness response
GET /assets/{key} Return a B2 hit or generate an authenticated miss
GET /api/assets/{key} Read stored media without generation
GET /api/runs?limit=20 List recent receipt summaries
GET /api/runs/{run_id} Return a canonical receipt
GET /api/verify/{run_id} Recompute artifact integrity
GET /reports/{run_id} Render a human-readable run report
GET /api/docs OpenAPI explorer

Deployment

The active service runs as a Docker web service on Render. Cloudflare Container and Railway configurations are also maintained. See Deployment for platform guides and post-deploy checks.

Security

Generation credentials stay server-side. Production generation fails closed without a configured bearer code, and quotas apply only to new B2 misses. Public receipts can carry prompt provenance, so confidential prompts must not be sent to a public deployment.

License

Qavelys is licensed under the MIT License.

About

Generative media origin that validates missing assets, stores full lineage in Backblaze B2, and serves durable cache hits.

Topics

Resources

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages