32°N Platform Engineering

Engineering

How I build this.

Single monorepo. TypeScript strict, no any. TDD — no implementation before a failing test. Every PR links a GitHub issue. AGPL-3.0 means every PR is public. This is how the project has stayed manageable across a growing surface area.

The engineering practices here are not aspirational. They are enforced — in CI, in code review, in the branch rules that prevent anything from merging to main without the checks passing.

01 · Test-driven development

No implementation before a failing test.

TDD is not a best-practice suggestion here — it is a hard rule. No behavioral change ships without a test that was red before the implementation made it green.

01

Write the failing test.

Define the expected behavior. Run the test. Confirm it fails for the right reason — not because of a missing import, but because the behavior is not there yet.

02

Write the minimum implementation.

Write enough code to make the test pass. No extra surface area. No "while I'm here" additions. Just the behavior the test specifies.

03

Make it green.

Run the test. If it passes, move on. If it doesn't, diagnose the implementation — not the test.

04

Commit the pair together.

Test and implementation ship in the same commit. Tests added after the feature works locally are not TDD — they are documentation.

What TDD applies to.

Component behavior. Route handlers. Business logic. Layout cascades. CSS tokens — if a token value changes, the visual regression test catches it. Every meaningful behavioral change has a failing test first.

The exceptions are narrow: trivial config edits, typo fixes, documentation-only changes, and UI iteration inside /playground/*. Once a playground component graduates to a real route, full TDD applies.

Why it matters on a boat platform.

The anchor watch needs to wake you at 3am if the boat drags. The weather router's isochrone math needs to give you the right passage window. The electrical planner needs to calculate load correctly before you leave on a long passage.

These are safety-adjacent surfaces. The engineering discipline is not abstract here. Tests that were red first mean the code actually covers the thing it claims to cover. Tests added afterward are retrofitted confidence — which is a different thing.

02 · TypeScript strict

No any. Anywhere.

TypeScript strict mode is on for every package. The any type is banned. Supabase-generated types are used directly — not re-typed in wrapper interfaces that can drift.

Why strict matters at the bus boundary.

The data bus carries typed events. A wind reading is not a generic object — it is a WindApparentEvent with a specific shape. If the publisher changes that shape, the TypeScript compiler tells every subscriber before anything runs. That is the bus contract enforced at compile time, not at runtime on the water.

The same applies across the platform. Apps talk to the platform through typed contracts. If the platform changes a service interface, the compiler finds every call site. No silent runtime failures from interface drift.

Database types generated, not hand-written.

Supabase generates TypeScript types from the live schema. The generated types are committed and re-generated whenever the schema changes. Hand-written database interfaces are not allowed — they always drift.

Row-level security is required on every Supabase table. No exceptions. A table without RLS is a review failure, not a "we'll add it later" situation.

Go is used for the performance-sensitive and stateful edge services — the bus-edge adapter, the NMEA parser, the routing math. TypeScript for everything that targets the web runtime.

03 · Monorepo

pnpm workspaces, Turborepo, one repo.

Everything lives in a single repository: the marketing site, the platform services, the apps, the shared libraries, the cloud services, the specs, the supabase migrations. One history, one CI pipeline, one place to look.

apps/site

Marketing site and hosted tools. Astro 5, React 19, deployed to Netlify. The 32°N public-facing surface.

apps/admin

Owner-only admin interface. Not public-facing.

apps/svc-*

Cloud services on Fly.io — weather data, routing, AIS ingest, relay, AI gateway, PKI, scanner, docs. Go for persistent-IP and stateful work.

packages/bus

The core event bus library. Schema definitions, typed event constructors, subscriber interfaces. Used by every app and service.

packages/chart-app

The chartplotter. MapLibre, PMTiles, IndexedDB-backed tile and route cache. Mounted per-boat at /boats/{slug}/chartplotter.

packages/electrical

Energy planner and wiring diagram components. The canonical wiring diagram is here — never reinvented in a simplified version elsewhere.

packages/weather-service

Weather provider abstraction and isochrone math. Routing performance test lives here too — known to be CI-flaky due to the 6s budget.

packages/tide-engine

Harmonic tide math. Standalone package — can be used without the rest of the platform. Every tidal calculation goes through here.

supabase/

Migrations and local config. Migrations auto-apply on first volume init; new migrations need manual catchup after volume reuse.

04 · Marine-specific testing

Test strategies for a boat platform.

Standard unit and integration tests cover the usual ground. But a platform that reads physical sensors, runs routing math on real meteorological data, and handles safety-adjacent alerting needs some specific approaches.

Simulated NMEA-2000 streams.

The bus tests do not require real hardware. I maintain a library of NMEA-2000 capture logs from actual passages — anchoring in a swell, motoring through an anchorage, a night passage with shipping traffic. The parser and bus tests replay those streams.

This covers the edge cases that are hard to hit on a test bench: GPS signal drop-out and re-acquire, sentence fragmentation at low baud, multi-talker collisions, instruments that disappear from the network and come back. The real data has all of those.

Weather routing performance budget.

Isochrone routing is computationally expensive. The test suite includes a performance budget: the routing pass for a 400nm passage with a 10-day GRIB file must complete within 6 seconds. On development hardware it runs in about 200ms.

The CI budget is intentionally conservative — CI runners are slower and more variable than local hardware. Occasional failures at the CI boundary are infrastructure variance, not regressions. A failure that reproduces locally is a real problem.

AIS scenario testing.

Collision avoidance logic is tested against recorded AIS scenarios: crossing situations, overtaking, head-on approaches, and the ambiguous cases that sit near the COLREGS boundaries. The scenarios are logged positions with manually verified correct responses — the test asserts the algorithm agrees.

Real hardware in the loop.

Some things cannot be tested without hardware. The hub setup process, the NMEA gateway pairing flow, and the Victron BLE adapter get tested on the actual hardware they target. Those tests are not in CI — they are in a manual test checklist before every release.

This is honest about the limits of automation. The goal is not to automate everything. The goal is to automate everything that can be automated, and be explicit about the rest.

05 · Security CI

Security checks in every PR.

I spent a decade in software supply chain security. The CI pipeline reflects that. Security checks are not an afterthought; they are mandatory gates.

Dependency scanning on every PR.

Every PR scans all workspace package.json files against the known CVE database. High-severity findings block merge. Moderate findings are surfaced but do not block — they require an explicit acknowledgment in the PR description.

Secret scanning on every push.

Gitleaks runs on every push. A committed secret blocks the push — not the PR. The earlier the catch, the less the blast radius. Patterns cover API keys, private keys, Supabase tokens, cloud provider credentials, and a custom ruleset for 32°N service tokens.

Container hardening for cloud services.

Every container image for the cloud services goes through Trivy before it lands on the registry. Base images are pinned to digest, not tag. No latest. No running as root in containers where it can be avoided.

RLS verification on schema changes.

Any migration that creates or alters a table is reviewed for RLS coverage. The CI pipeline checks that every table in the schema has at least one RLS policy. A table without RLS is a hard blocker.

Static analysis across the TypeScript surface.

TypeScript compiler in strict mode covers most of the type safety ground. ESLint with the security ruleset runs in CI and catches patterns the type system does not — dangerous eval patterns, missing sanitisation in template contexts, prototype pollution candidates.

Marine Security Scanner as a first-party app.

The scanner application — a full progressive marine pentest tool — is a first-party app in the monorepo. I use it on the platform itself. If the scanner finds something on a 32°N install, that is a real issue I need to fix.

06 · AGPL and the PR culture

Every PR is public.

AGPL-3.0 means the whole engineering process is visible. Every feature branch, every PR, every review comment, every merge is in the public repo. That changes how I work.

Feature branches, not commits to main.

Nothing merges directly to main. Every change — including one-line fixes — lives on a feature branch and goes through a PR. The PR links a GitHub issue. The issue is in the public backlog. The whole trail is auditable.

Feature branches live in their own git worktrees. This is a practical rule: it means the primary tree stays on main, always clean, always ready to pull in an upstream change without a stash dance.

The issue is the authoritative specification.

GitHub Issues are the backlog. Not a private Notion, not a committed markdown file, not a verbal agreement. The issue is where the requirement lives, where the discussion happens, and where the PR closes it. If the issue is not public, the change should not be on main.

I am not taking outside contributions before 1.0 — the API surface is still moving too fast. After 1.0, the contribution process will be in the repo with clear terms. The AGPL means the terms have to be consistent for everyone.

Read the source, or explore the platform.

The repo has the code, the open issues, and the PR history that shows these practices in action. The platform overview puts the engineering in context of the full stack.