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
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.
No login is needed for any read-only proof route.
Web path:
- Open the verified run report.
- Read the B2 object paths, provider and model, quality decision, and the two hashes.
- 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 hashGeneration 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.
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 |
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 |
| 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 |
Mobile captures are in docs/screenshots.
| 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 |
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- 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 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" }
}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
See Architecture for object layout, trust boundaries, receipts, and request sequencing.
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 8000Frontend in another terminal:
cd frontend && npm ci && npm run dev # http://127.0.0.1:5173| 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.
| 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 |
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.
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.
Qavelys is licensed under the MIT License.



