active
Hawzu
Execution-first test management for QA teams.
- Role
- Founder & sole engineer
- Period
- Apr 2025 — Present
- Stack
- PythonReactCloud infrastructureMongoDBFastAPIRedisTypeScriptAstro
- 4 Web properties
- 2 API surfaces
- 1 Engineer
Most test management tools are, underneath, a database of test cases with reporting bolted on. They answer “what did we test?” well. They answer the question every release meeting actually asks — are we ready to ship? — badly, or not at all.
Hawzu is built the other way round. Execution is the centre of the model, and everything else exists to turn execution into a defensible shipping decision.
The shape of it
What I built
The whole thing, end to end. There is no team.
Backend — FastAPI over MongoDB, with Redis as a cache. Two API surfaces
share one middleware chain: /ui/v1 for the app, and a public /api/v1 for
automation. Automation authenticates with scoped service accounts rather than
user sessions. They behave similarly, but their lifetime belongs to the
automation, not to a person’s browser session.
One small decision illustrates the philosophy: the middleware is ordered so that failures stay truthful. Authentication and authorization can both reject a request, and when they do the caller needs to see that — not a transport-level error standing in front of it. An outage you can’t name is worse than the outage.
Frontend — a React + Vite SPA on Mantine, plus three static Astro sites (marketing, docs, blog) that deploy independently to CDN-backed buckets.
Infrastructure — one deployable API. Everything in front of it is static and ships on its own schedule, behind a single shared edge. A docs fix or a blog post goes out without redeploying the product, and nothing on that side can take it down.
Decisions worth writing down
Execution before storage
The data model treats a test execution as the primary record and the test case as the thing being executed. That inversion is why release readiness is computable at all: coverage, defects, and requirement traceability all hang off executions, so the evidence is already connected when someone asks whether to ship.
What it cost: almost every query is scoped through an execution, so the handful of genuinely test-case-centric views — “where is this case used?” — are the awkward ones. That was the right trade only because the shipping question is asked far more often than the inventory question.
A release snapshot, not a moving target
Release analytics used to shift under you: link a defect after sign-off and last month’s numbers changed.
The obvious fix — lock defects once a release completes — was wrong. Defects
should keep moving; a bug found in production after sign-off is still that
release’s bug, and its status genuinely changes. What must not move is the
evidence the decision was made on. So the release, its executions and their test
cases are held read-only by the editability guards while frozen, and the one
remaining source of drift — the defect-derived analytics — is snapshotted at the
moment the release completes, with the snapshot’s as_of becoming the frozen
clock everything downstream reads from.
What it cost: a second read path. Every consumer of release analysis has to know whether it’s looking at live data or a snapshot, and the UI has to say which it’s showing. That’s real complexity bought to keep one property: sign-off means something only if the evidence behind it stops moving.
Jobs in Mongo, not a broker
The background queue started on Redis/RQ and moved into MongoDB — the database already being operated — drained by a bounded thread pool inside the API process itself. Redis stayed, as a cache only.
The interesting part isn’t the store, it’s what Cloud Run does to the design. Every replica has the scheduler enabled, and Cloud Run autoscales, so every replica tries to drain the same queue. Rather than run a separate worker service, correctness comes from a Mongo-backed advisory lock with a lease and heartbeat: whoever claims a job runs it, a live holder keeps its lease fresh, and a holder that dies has its lock taken over once the lease goes stale. The same mechanism guards the periodic sweeps, which would otherwise run once per replica.
What it cost: the lock is now load-bearing infrastructure — a correctness bug there is a double-executed job, not a slow one. In exchange there’s no worker tier to deploy, scale or monitor separately.
A cache you can lose
Redis holds sessions, user metadata and permission lookups. When it isn’t there — at startup or halfway through a request — lookups fail fast and fall through to MongoDB rather than waiting on a connection that isn’t coming back. Slower, still correct.
The rule this encodes: if losing the cache takes the product down, it was never a cache. It was an undocumented database.
Each of these left behind something more general than itself. The versions I’d carry to the next system are written down as principles.
Where it is now
In early access, bootstrapped, with every feature free while the product finds its shape.
Currently in development, not yet shipped: CI/CD automation — triggering and importing builds from Jenkins, GitHub Actions and GitLab CI. The design problem that made it interesting is what to do with results that match no known test case. Discarding them loses data; force-matching them invents coverage nobody ran. They get their own collection instead, deliberately outside the one the analytics read from, so an unreconciled pile can never quietly become a number on a readiness scorecard.