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.

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, andpercentagefor deterministic rollout. all/any/nonecombinators 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 +
localStoragecache with corruption recovery, SSE primary + polling fallback, synchronous never-throwing reads, lifecycle events. - React adapter —
useBool/useString/useNumber/useJson/useFlaghooks viauseSyncExternalStoreso only components reading a changed flag re-render. - PHP / Laravel — Laravel service provider auto-registers;
@pennantBlade directive andPennantfacade. - Python / Django / FastAPI — Django middleware +
flag_requireddecorator 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) andpn_clt_for browser-safe pre-evaluated snapshots (rules never leave the server). - Filament v5 admin shell at
/adminfor 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


