Skip to main content
Back to Portfolio

Pennant

Feature flag and remote-config service with a targeting rule engine, real-time SSE push, multi-environment workflow, audit log, and four SDKs that share a cross-implementation drift corpus.

Laravel 13PHP 8.3MySQLRedisFilament 5Next.js 16ScalarTailwind 4OpenAPI 3.1TypeScriptReactPythonServer-Sent Events
Pennant preview

Overview

A portfolio build shaped for product teams. Where webhook-relay sells to API teams (HMAC signing, retries, dead-letter queues), Pennant is for the buyer evaluating "buy vs build" on flag infrastructure — and the engineer who has to make sure the rule engine on the server agrees with the rule engine in the browser about who's in the rollout.

The headline move:

The TypeScript SDK and the Laravel server both implement the same JSON-expression rule engine + the same SHA-256 bucketing function. tests/corpus/rules.json is a fixture of thirty-four rule cases plus five bucketing cases that runs through every implementation in CI — PHPUnit on the server, Vitest in the TS SDK, PHPUnit again in the PHP SDK, pytest in the Python SDK. If any implementation disagrees about a single tuple, CI fails on all four simultaneously. Silent rule-evaluator drift between server and SDK is the #1 way a flag system corrupts experiment data — the corpus catches that drift before it ships.

Rule engine:

  • Ten operators — equals, not_equals, in, not_in, contains, not_contains, starts_with, ends_with, regex, numeric and date comparisons, and percentage for deterministic rollout.
  • all / any / none combinators with arbitrary nesting, depth-limited to thirty-two for cycle protection.
  • Reusable segments referenced from any flag's rules, with static cycle detection on segment writes.
  • Dotted attribute paths (address.country) for nested context lookup.

Real-time delivery:

Redis pub/sub publishes a small envelope on every flag-configuration write. A standalone Node SSE broadcaster (~160 LOC, supervisord-managed) holds long-lived text/event-stream connections, scans a 100-event ring buffer for Last-Event-ID replay on reconnect, and forwards each event as an SSE frame. Apache reverse-proxies /v1/stream to the broadcaster with ProxyTimeout 86400 so long-lived connections survive intermediate idle timeouts.

SDKs (four shipped, one contract):

  • TypeScript core — bootstrap snapshot, in-memory + localStorage cache with corruption recovery, SSE primary + polling fallback, synchronous never-throwing reads, lifecycle events.
  • React adapter — useBool / useString / useNumber / useJson / useFlag hooks via useSyncExternalStore so only components reading a changed flag re-render.
  • PHP / Laravel — Laravel service provider auto-registers; @pennant Blade directive and Pennant facade.
  • Python / Django / FastAPI — Django middleware + flag_required decorator and a FastAPI dependency.

Vue, Svelte, Go, Ruby, JVM, Swift, .NET ports are documented as v2 work at /sdks/_roadmap.md — gated on actual buyer signal rather than speculative coverage.

Surface:

  • 23 OpenAPI operations under /v1/ covering flag and configuration CRUD, segments, evaluation, snapshot bootstrap, SSE stream, audit, and API key minting.
  • Two API key kinds — pn_srv_ for server SDKs (see raw rules) and pn_clt_ for browser-safe pre-evaluated snapshots (rules never leave the server).
  • Filament v5 admin shell at /admin for workspace + user management; per-resource CRUD UIs slated for the next polish pass.
  • Audit log on every flag, configuration, and segment mutation, with actor + diff + optional reason field.

Stack:

  • Laravel 13, PHP 8.3, MySQL 8, Redis, Filament v5, Apache + mod_php, Let's Encrypt
  • Node 22+ SSE broadcaster (ioredis) under supervisord
  • Next.js 16 docs site with Scalar-powered /reference, Tailwind 4 styling, interactive bucketing visualizer
  • PHPUnit + Vitest + pytest for the four-SDK drift corpus

What it proves:

Same person hand-authored the OpenAPI spec, wrote the Laravel controllers, designed the rule-engine corpus, ported the evaluator to TypeScript / PHP / Python, built the React adapter, wrote the SSE broadcaster, scaffolded the Next.js docs site, deployed all three processes to EC2 with atomic releases + supervisord + PM2 + Let's Encrypt, and shipped it live. The case for hiring me to build a single coherent flag service instead of stitching together a backend dev + frontend dev + DevOps + SDK author + tech writer.

Results

  • 33 PHPUnit + 45 Vitest + 2 PHP-SDK + 39 Python-SDK + 6 React-adapter tests, all passing

  • 34-case rule corpus + 5 bucketing cases round-tripped through Laravel server and all four SDKs in CI

  • Four SDKs from one spec — TypeScript core, React adapter, PHP/Laravel, Python/Django/FastAPI

  • Sub-200ms flag-change propagation over SSE; offline-resilient client cache; deterministic local bucketing

Gallery

Pennant screenshot 2
Pennant screenshot 3
Pennant screenshot 4

Interested in working together?

Let's discuss how I can help with your project

Get in Touch