How it's built
Clearspar Heli is a portfolio prototype — a day-of-operations system for a heli-ski or heli-charter operation, covering scheduling, live flight-following, safety and compliance logging, and team comms. Every aircraft, guide, and guest in the demo is invented sample data; it is not a real operator and not a certified operational safety system. What is real is the engineering: the architecture, the isolation model, the failure modes found and fixed, and the verification behind them. This page is the short version.
What it's built on
- FastAPI + Uvicorn (Python, async)
- Async SQLAlchemy 2.0 over Postgres (asyncpg); aiosqlite for local dev
- Pydantic 2 for schemas and settings
- Argon2 password hashing
- AFF (Automated Flight Following) position-ingest adapter — Spidertracks/TracPlus/SKYTRAC standard, sample-gated
- Redis for optional cross-worker pub/sub
- Next.js App Router, statically exported (output: "export")
- Served same-origin by the FastAPI backend
- MapLibre GL for the live map, over real Esri satellite imagery
- Tailwind; a hand-rolled markdown renderer (no markdown dependency)
- Docker containers on a self-managed Linux host
- Live at heli.binnacleai.com
- Server-Sent Events for live updates (in-process, or Redis across workers)
- Own-the-stack error tracking — no third-party APM
Problems worth writing up
Tenant separation is enforced by Postgres Row-Level Security, not by application-side WHERE org_id filtering. Each request sets a transaction-scoped GUC (SET LOCAL app.current_org_id) that the RLS policies key off; the app connects as a non-superuser role with FORCE ROW LEVEL SECURITY. If the variable is never set, the database returns zero rows rather than leaking — it fails closed.
Read: Row-Level Security, and the bug that fails by disappearing →A dedicated load test fires interleaved requests from different organizations through the same async connection pool — the exact scenario the SET LOCAL design exists to survive, and one sequential tests never exercise. Each org's rows carry an owner-encoding reference, so any foreign reference in a response is an unambiguous, decodable leak rather than a fuzzy count mismatch. Run against real Postgres, the pass records 20 of 20 with zero cross-org leakage.
One table — the native error log — is deliberately kept out of RLS, because an error can be captured before any org context resolves. That exemption quietly made a role check the only thing standing between two tenants' error rows. The fix was to move enforcement to the read boundary: a manager sees only their own org's rows, and only the pre-existing platform-admin capability sees the global view. An internal verify pass caught it before it could matter.
Read: The one table I took out of Row-Level Security →Aircraft positions come from an AFF (Automated Flight Following) adapter — the open aff.gov XML-over-HTTPS standard that Spidertracks, TracPlus, and SKYTRAC/Latitude all speak, so one adapter covers many vendors. Both halves are real: an authenticated HTTPS transport and a namespace-tolerant XML parser that reads position, altitude, speed, and heading and maps each device serial to an aircraft. It runs a clearly labeled bundled sample feed by default and flips to a live operator feed with three environment variables and no code change; an optional OpenSky ADS-B feed supplements it as a fill-in. Every fix is stamped with its source, so the map can never show a sample as live.
Background pollers — aircraft position tracking, check-in escalation — use Postgres advisory-lock leader election so exactly one worker runs each named job even with several workers behind the load balancer. Live updates ship over Server-Sent Events, in-process by default and swappable to Redis pub/sub across workers. Real external feeds are wired in: NWS/METAR weather, OpenSky Network ADS-B, and an avalanche.org/CAIC advisory feed.
Integrations that would need real credentials are stubbed and clearly labeled rather than faked. The Twilio SMS escalation path never texts a real phone, billing tiers only change on a real webhook, and the avalanche-danger widget is an operator-entered hazard plainly badged as not an official forecast — shown beside a real avalanche.org/CAIC feed that reads "not mapped" rather than inventing a rating. The offline UI shows a truthful "N changes queued, last synced Xm ago" state instead of a false all-synced. The rule — never claim something the code doesn't do — is enforced down to the marketing copy.
The safety surface is built on records that can't be quietly rewritten: an append-only audit log of every mutating action, append-only GPS breadcrumb history with scrub-and-replay, and immutable sign-off records. On top of those sit a Part 135-style duty-hours log, an MEL/squawk discrepancy board, a guide certification and currency tracker, an incident/near-miss log, and pilot-attributed weight-and-balance sign-offs.
The field-facing app is a progressive web app: a service worker caches the shell so it loads without a connection, and writes like a check-in are queued in IndexedDB and replayed once the network returns. Getting the service worker right in development — where an unstamped cache collided with the dev bundler and triggered a full-page reload loop — was its own debugging story.
Read: The reload loop that reproduced on pages it never touched →The public webhook feature lets a manager register an outbound URL. The guard resolves the hostname and rejects private, loopback, link-local, reserved, multicast, and unspecified addresses — and re-checks at delivery time, not only at save time, specifically to defend against DNS rebinding, where the answer can change between when a URL is saved and when it's called.
Engineering practice
The features above didn't ship on optimism. Each wave closes with an adversarial verification pass over the already-live app, looking specifically for behavior that is actively misleading rather than merely missing — and those passes have found real bugs, including the cross-tenant error-log leak and a date-handling bug several earlier build agents had waved off as a flaky test. Access control is documented as a role-by-capability permission matrix that mirrors the actual server-side checks for every role, so the reference and the enforcement can be compared directly. An accessibility audit corrected real contrast failures. Dedicated Postgres RLS proofs cover each isolated table, and a 1,300+-assertion smoke suite guards against regressions across the whole surface. Two companion native SwiftUI iOS apps — a guest view and a field view — are built against the same backend.
The engineering writeups
A cross-tenant read leak born from two individually-correct decisions, caught by an internal verify pass and fixed at the read boundary.
A dev-only service-worker cache collided with the bundler's hot reload — a symptom that pointed nowhere near its cause, root-caused end to end.
How multi-tenant isolation is enforced at the database level, and the silent failure mode that design keeps producing.
A mutating action shipped with no role check at all — how it was found, fixed to match the adjacent action, and verified against the live API.
Why a simulated day-of-ops platform forces more interesting engineering decisions than a typical portfolio CRUD app.
The fastest way to judge the work is to use it. Sign in with any sample role on the demo and watch the same live state drive every module.