Skip to content
WorkShippedLive, web app installable as a PWA

Diwalco

Founding engineer on a private family-tree and family-feed platform, from the API and data model to deploy-and-rollback.

Overview

Diwalco helps families build a shared family tree, invite relatives to claim their place in it, and share memories privately. I'm the core engineer and majority contributor: the Express/Bun API, realtime chat, referrals and contests, offline-first client runtime, CI/CD, observability and release safety.

Company
Diwalco Limited (New Zealand), remote
Role
Founding Software Engineer, May 2025 - Present. Joined as the current platform was being built.
Ownership
Core engineer and majority contributor. 42 PRs authored, 40 merged; work peer-reviewed from Feb 2026.
Product
A private, collaborative family tree and family feed; a web app, installable as a PWA
Surfaces
Consumer web app (PWA), admin console, REST and realtime API

Stack

  • Bun 1.4
  • Turborepo
  • Express 5
  • Socket.IO
  • Zod
  • Drizzle ORM
  • PostgreSQL 16
  • Neo4j
  • Next.js 16
  • React 19
  • TanStack Query
  • SWR
  • IndexedDB
  • AWS
  • Docker Compose
  • GitHub Actions
  • GHCR
  • Trivy
  • Sentry
  • PostHog

Context

“One app for your family's past, present, and future.” You build a family tree for free starting from yourself, invite relatives who claim their own node (with admin approval), belong to several trees (your father's side, your mother's side), and share photos, milestones and memories in a private family feed. Trees have Admin, Moderator and Member roles, and users own their data.

The engineering shape: family relationships are a graph, but memberships, referrals, payouts and consent need relational integrity. Users are mostly on phones and patchy networks. Growth runs on invites, referrals and a milestone-based referral contest, which brings fraud and abuse pressure.

How to read the labels

Work is presented as it actually exists. Every claim on this page carries one of these labels.

Shipped
Merged to the production branch
Pre-release
Merged to the default branch, awaiting the next promotion to production
Design
Architecture and design complete; not yet implemented

What I owned

API (Express 5 on Bun): 96 versioned endpoints across 12 modules, shared response envelope, Zod validationMajority contributorShipped
Auth: Cognito JWT/JWKS verification on HTTP and sockets, verified-token and user caches, phone OTP sign-inAuthorShipped
Realtime chat (Socket.IO): authenticated sockets, membership-gated rooms, typing, read receipts, typed shared event contractsAuthor (client and server)Shipped
Memories feed: posts, multi-photo carousels, likes, threaded comments, shareAuthorShipped
Invites, referrals, milestone contests, auditable grand-prize draw, UPI prize claimsAuthor (admin contest config co-built)Shipped
Offline-first client runtime: unified API clients, endpoint registry, user-scoped IndexedDB cache, service-worker rewriteAuthor, on a teammate's PWA foundationShipped
Versioned policy consentAuthorShipped
Policy evidence (append-only, hash-bound, release-aware)AuthorShipped
CI/CD, containers, secrets, deploy and rollbackOwner of all pipeline, Docker and host scriptsShipped
Test suite and CI contract gatesIntroduced and own them; tests generated with AI, reviewed by me before mergeShipped
Observability: Sentry in 3 apps, manual tracing on Bun, credential redaction and replay scrubbingAuthorShipped
Bounded image pipeline: browser worker pool, server admission, WebP renditionsAuthorShipped
In-app bug reporting with my npm package react-bug-reportAuthorShipped
Neo4j introduction, driver package, refactor of the cross-store transaction helperAuthor / refactorShipped
PostgreSQL to Neo4j transactional-outbox projectionArchitecture and design complete; not yet implementedDesign
AI-assisted family-tree creationDesigned and implementedPre-release

Architecture

Client

Next.js 16 PWA (web)

SWR and TanStack Query, a user-scoped IndexedDB cache, Web Worker image renditions

HTTPS /api/v1 and Socket.IO

API

Express 5 API on Bun

Request pipeline: request-id → rate limiters → Cognito JWKS auth → Zod → services

Ordered cross-store commits

Data and services

PostgreSQL 16

System of record

Neo4j

Family graph

AWS S3

WebP renditions

Cognito, SES, Secrets Manager

Identity, email, secrets

Observability: Sentry across web and API, plus PostHog.

PostgreSQL is the system of record; Neo4j holds the family graph. Writes commit to both in a fixed order.
  1. Pull request

    Typecheck, 12 contract gates and tests

  2. Multi-arch image build

    BuildKit secret mounts keep secrets out of layers

  3. Trivy gate

    HIGH and CRITICAL findings stop the release

  4. Scan-tagged digests

    Only scanned digests become release tags

  5. Release candidate

    Resolved per commit and re-validated

  6. Signed manifest

    Ten files describe exactly what deploys

  7. EC2 host, Docker Compose

    Serialized deploy with atomic release and config switches

  8. Health and readiness

    Both datastores must answer before traffic moves

    On failure: Automatic rollback. Phase-aware, and held to the same readiness bar

Every release is scanned, signed and promoted by digest, and rolls back automatically if readiness fails.

Deep dives

Two stores, one source of truth

ShippedDesign
Problem
Family relationships need graph traversal; memberships, referrals and payouts need relational integrity.
Approach
PostgreSQL is the system of record: a 42-table schema with partial unique indexes for business invariants such as “one accepted referral per invitee” and “one DM per user pair”, plus CHECK constraints on money and state machines. Neo4j holds the family graph (people, trees and unions as nodes). I introduced Neo4j with a bounded, validated driver package and refactored the cross-store transaction helper. Writes commit in a fixed order, with rollback on any failure before commit.
Honest boundary
This is not an atomic distributed transaction; a narrow window remains between the two commits.
Next step
I architected and designed a PostgreSQL to Neo4j transactional-outbox projection: ordered graph projection, idempotent retries and a graph that can be rebuilt from PostgreSQL. The design is complete; it isn't implemented yet.

AI-assisted family-tree creation

Pre-release
Problem
Typing a family tree node by node is tedious; people naturally describe their family.
Approach
The workflow takes a natural-language family description, uses an LLM only to interpret it into structured people and relationships, runs deterministic application validation, shows the result for user review and correction, and persists only the confirmed tree across PostgreSQL and Neo4j.
Principle
The model interprets; validation and the user decide what's written. Schema-valid output isn't the same as a correct family.
  1. DescribePerson

    The user writes their family in plain language

  2. InterpretModel

    An LLM turns it into structured people and relationships

  3. ValidateCode

    Deterministic application rules check the structure

  4. ReviewPerson

    The user corrects and confirms the tree

  5. PersistCode

    Only the confirmed tree is written, across PostgreSQL and Neo4j

Nothing the model produces is written until it passes validation and the user confirms it.

Correctness under concurrency

Shipped
Contest enrolment race
Concurrent requests read stale winner counts and could over-award a capped milestone. Fixed with a row lock (SELECT … FOR UPDATE) inside the enrolment transaction.
Double referral attribution
Phone verification did a read-then-write. Replaced with a single conditional UPDATE … WHERE not-yet-verified RETURNING.
Referral completion stuck pending
A nested call took a second pool connection while a transaction held one: a connection self-deadlock. The side effect now runs after commit.
Read receipts
The read cursor is monotonic (time plus a UUIDv7 tiebreak) and broadcasts only when it actually advances.
Grand-prize draw
Idempotent (row lock and one award per contest), fraud-flagged users excluded in SQL, deterministic candidate ordering, an injectable crypto RNG for tests, and a SHA-256 hash of the candidate set stored and audit-logged in the same transaction.

Identity and abuse resistance

Shipped
Verification
Cognito JWT verification against JWKS with a pinned issuer, token_use and client-ID binding, on both HTTP and the socket handshake.
Speed
Bounded LRU caches for verified tokens and user rows, with reverse-index invalidation by user ID. Observed average authenticated request time fell from ~1.8 s to ~0.4 s (observed, not an SLO). Infrastructure outages (database, JWKS) surface as 500s instead of masquerading as 401 sign-in churn.
Abuse controls
10 purpose-built rate limiters (global, chat, mutations, contest polling, OTP send keyed by destination, OTP verify, referral lookups, signup attribution, feedback, friend invites). Turnstile on OTP send, timing-safe internal-secret comparison, and unbiased cryptographic referral codes with collision retry.
Hardening
Turnstile action and hostname binding (blocks cross-site token replay), and a per-destination SMS limiter on the auth routes for toll-fraud protection beyond IP limits.

Release engineering with honest rollback

Shipped
Scan before tag
Images are pushed to throwaway scan tags and gated by Trivy (HIGH/CRITICAL); only scanned digests become release tags. SBOM and provenance attestations; all 81 third-party Actions pinned by commit SHA; BuildKit secret mounts keep build-time secrets out of image layers.
Promote, don't rebuild
Production reuses the pre-release server image only if a diff over every build input is empty; otherwise it rebuilds and rescans.
Deploy
A release candidate is resolved per commit, re-validated and described by a signed 10-file manifest. The host deploy is serialized by a lock, with atomic release and config “generation” switches, then health and readiness checks, then phase-aware automatic rollback held to the same readiness bar. Database migrations are deliberately forward-only, so schema changes must be backward compatible.
Secrets and containers
AWS Secrets Manager syncs to 15 file-backed Docker secrets, written with symlink refusal, descriptor-based (TOCTOU-safe) validation and an atomic generation flip. Containers run with a read-only root filesystem, tmpfs, all capabilities dropped, no-new-privileges, a non-root user, CPU, memory and PID limits, and digest-pinned base images.
Builds and hosting
Native amd64 and arm64 builds (replacing QEMU emulation within a day), a private-network deploy path, GHCR retention cleanup, and a Free-plan artifact-quota guard.

CI that tests its own checks

Shipped
From zero
Before March 2026 the repo had no tests and no CI. I introduced both: 0 → 160 test files (measured 2026-10-02). The tests are generated with AI and every one is reviewed by me before merge; the gates below are what keep them, and the rest of the code, honest.
Contract gates
3 → 12 gates: API-contract drift, toolchain and dependency-catalog parity, env example, smoke and schema parity, secrets-registry parity, release-payload and config-path parity, and public-asset budgets. Migrations are applied to a fresh PostgreSQL in CI and fail on schema drift.
Checks that check
A meta-gate fails CI when any check:* script exists that isn't wired into the pipeline. Gate self-tests copy real files, mutate one line and assert the gate fails; vacuous-assertion guards stop a scan from passing by matching nothing.
Why
Partly to catch AI-generated code drifting from architectural rules. I also wrote a rulebook for AI coding agents whose rules map to these gates.

Observability on a runtime the SDK doesn't fully support

Shipped
Manual tracing
Sentry runs across web, admin and API. Bun reimplements node:http, so OpenTelemetry auto-instrumentation yields no Express transactions; I added manual request tracing with browser trace propagation.
Source maps you can trust
Bun's own debug IDs collided with Sentry's, so the build strips, injects and verifies them. The uploaded source map is checked byte-for-byte against the server.js in the scanned image.
Sampling by measurement
Health checks, session polling and static assets were ~83% of observed trace volume (development observation), so they're excluded from tracing. A 15-second health check alone would otherwise create ~5,760 transactions per container per day (calculated). Session Replay runs only on errors, with all text and inputs masked.
Redaction
One shared credential redactor applied at four SDK hooks, replacing three drifted copies, plus a transport that scrubs replay envelopes.

A bounded image pipeline

Shipped
Browser
Reads dimensions from the header without decoding, resizes during decode in a module Web Worker on OffscreenCanvas, and uses a worker pool capped at 3 (2.85× at 3 vs 2.91× at 4), sized down by device memory. 2.80× faster multi-photo conversion in a controlled desktop Chromium benchmark (6 × 12 MP JPEGs: 4,183 ms → 1,495 ms).
Server
Capacity is reserved before any byte is buffered (global, per-user and bytes-in-flight ceilings), with abort-aware multipart parsing, a separate lane for heavy images, one WebP rendition (2,160 px edge) re-encoded server-side, and orphaned uploads cleaned up even on aborted requests and during shutdown. Web and API share one upload contract.

Offline-first without leaking data between users

Shipped
The service worker never caches /api, because URL-keyed caches carry no user identity. API reads are cached in IndexedDB under a hashed user scope with TTL and byte budgets, sensitive paths excluded. Writes fail fast offline, and every cache is wiped at each auth boundary. One endpoint registry (78 endpoints) and three allowed transports are enforced in CI.

Graceful shutdown on Bun

Shipped
Shutdown releases sockets, database pools and in-flight upload cleanups with a deadline, and readiness checks both datastores.
Finding
On Bun, open WebSocket connections must be force-closed in the same tick as io.close(), or the HTTP server never finishes closing.

Talking to families

I led early product conversations with individuals and families: introduced and demonstrated the product, gathered feedback, and helped people sign up and get started.

Smaller wins

Replaced ~580 lines of hand-rolled feedback UI with my open-source react-bug-report (shipped).
Reinstated versioned database migrations with a fresh-database CI check (shipped).
Migrated auth from a third-party auth provider to AWS Cognito (shipped, Aug 2025).
Built the v1 marketing site from scaffold to public launch in 2025 (since redesigned by a teammate).

Timeline

  1. May 2025Joined; v1 marketing site
  2. Jun 2025First API: PostgreSQL schema, Express routes, Zod validation, invites with open links and join requests, S3 uploads
  3. Jul 2025Monorepo restructure; Neo4j added alongside PostgreSQL; shared types and logger packages
  4. Aug 2025Auth moved to AWS Cognito; marketing site goes live
  5. Sep - Nov 2025Invite dashboard; Memories feed (likes, threaded comments, carousels); profiles with photo cropping
  6. Jan 2026Realtime chat
  7. Feb 2026Referral system, contest enrolment, phone OTP sign-in, referral fraud checks
  8. Mar 2026Offline-first API runtime, response envelopes, versioned consent, first CI workflow and contract gates
  9. Apr - Jul 2026Milestone contests, invite analytics, ownership and membership integrity, graceful shutdown, configurable milestone contests, grand-prize draw
  10. Aug 2026Production CI/CD (env contracts, images, scan gates, signed deploys, rollback); Sentry in 3 apps; bug reporting
  11. Sep 2026 onwardBounded image pipeline, native multi-arch builds, an artifact-quota guard, security hardening and the consent-evidence stack; the AI-assisted tree builder in pre-release

Numbers

Measured 2026-10-01. Each figure says what it counts; none of them are user, revenue or uptime claims.

PostgreSQL tables (108 indexes, 14 CHECK constraints, 72 foreign keys)
42
Shipped
Versioned REST endpoints / route modules
96 / 12
Shipped
Purpose-built API rate limiters
10
Shipped
Automated test files, generated with AI and reviewed by me (measured 2026-10-02)
0 → 160
Shipped
CI contract gates, plus a meta-gate
3 → 12
Shipped
GitHub Actions workflow files; 81 of 81 third-party actions pinned by commit SHA
6
Shipped
File-backed Docker secrets synced from AWS Secrets Manager
15
Shipped
Apps instrumented with Sentry
3
Shipped
Observed trace volume that was health checks, polling and static assets (development observation), now excluded
~83%
Shipped
Observed average authenticated API request time after token and user caching
~1.8 s → ~0.4 s
Shipped
Multi-photo conversion throughput, controlled desktop benchmark
2.80×
Shipped
Lines of custom feedback UI replaced by react-bug-report
~580
Shipped

What I'd do differently

  • Put tests and CI contract gates in from the first commit. They arrived ten months in, so they had to fit around decisions that had already set.
  • Decide how two datastores stay consistent before adding the second. Neo4j arrived with ordered commits and rollback; the outbox design that closes the remaining window came later.
Next case studyCodex GuardAn LLM guardrail for code review that can't be talked out of its own rules.