Merge pull request 'docs(ops): feature flags are not worth a manager here (#318)' (#329) from spike/318-feature-flags into main
Linting / lint (push) Successful in 2m49s
SonarQube Analysis / sonarqube (push) Failing after 35m25s

Reviewed-on: #329
This commit was merged in pull request #329.
This commit is contained in:
2026-09-09 13:39:21 -05:00
+80
View File
@@ -0,0 +1,80 @@
# Feature flags: whether this project wants a manager
Findings for #318. Tool capabilities and pricing were checked on 2026-09-08; everything about this repository was read from the code on 2026-09-09.
**Status: do not adopt a feature flag manager. Not yet, and possibly not ever at this size.** This is written down so the question does not get reopened from scratch — including the conditions that would change the answer.
## The short version
This project already has two flag mechanisms, uses both idiomatically, and is not short of a third. What it lacks is a boolean in one of them, which is about twenty lines of work and needs a reason before it is worth doing.
No tool was compared until that was established, because a spike that starts by comparing platforms will always find one.
## What already exists
**Environment variables, for per-environment gating.** `DEMO_MODE`, `REMBG_URL`, `BREVO_TRACKER_KEY`, `ANTHROPIC_API_KEY`, the USPS and SMTP credentials. The convention is consistent and documented: unset means the feature does not exist, and the application is *working* rather than broken. QA runs with several deliberately empty, which is how it cannot reach PayPal, cannot email anyone, and cannot report browsing into the live Brevo account.
That is deploy-time gating, and it is genuinely a feature flag system — it just is not called one.
**`admin_settings`, for runtime tuning by whoever runs the shop.** A typed key/value store with an admin UI, ten settings, per-setting fallbacks, and one place to add another. Its own header says the point plainly: adding a setting means adding a row and nothing else. `drafting_model` and `intake_notify_email` are there rather than in the environment for exactly the reason a flag would be — they are changed by the person running the shop, not the person deploying it, and a redeploy to change an email address would be absurd.
The two are complementary and the split is already the right one.
## What is actually missing
`admin_settings` supports `hours`, `count`, `text` and `choice`. **There is no boolean.**
So the one thing a flag manager would give this project that it does not have — an admin-flippable on/off for a code path, without a rebuild — is a missing value type in a store that already does everything else. Adding it is a definition entry, a parse branch, and a control in the settings tab.
That is the whole gap. It is not a reason to run another service on the NAS.
## Is there anything to flag right now
No, and the issue said a spike that cannot name one should say so.
Nine issues are open. Seven are the Passkeys epic and #313, and neither wants a flag:
- **Passkeys (#37#42)** is naturally incremental — schema, then registration, then the authentication ceremony, then management, then the login page. Nothing is user-visible until #41 puts it on the login form, so the ordering does the gating that a flag would otherwise do. The epic has also been untouched since 2026-08-21 and its parent #36 is closed, so its status is undecided in a way no tooling addresses.
- **#313's `trust proxy` change cannot be flagged.** It is only correct once Cloudflare is actually in front, so it must ship with the DNS cutover. A runtime toggle would let it be wrong on purpose, which is worse than not having one.
**The strongest hypothetical is a kill switch for passkey login**, because #41 records that it is the one feature in the project that cannot be fully proven in QA — credentials bind to the RP ID, so production is the first place it runs for real. Being able to turn it off without a rebuild would have value there.
That is a real argument, and it is still not enough today: passkeys are not being built, and **production is not live**, so there is no customer-facing behaviour to protect. Revisit it when #41 is actually being written.
## What flags would buy here, honestly
The usual headline — decouple deploy from release — buys much less than it does elsewhere, because there is no CD. Every change already reaches production through a manual NAS rebuild, so the deploy is a deliberate act either way.
What is left is narrower:
- **A kill switch**, avoiding a rebuild. The strongest case, because the rebuild is this project's slowest and most error-prone step.
- **Shipping half-finished work** behind a flag rather than holding a long branch. Weak here: branches are short and merged the same day.
- **Per-environment behaviour** without another env var. Already solved, by the env vars.
Against all of it: every flag is a branch in the code that has to be removed later, and stale flags are their own debt. For a single-developer shop, that cost is paid by the same person who would benefit.
## If a tool is ever wanted
Constraints that decide it: the NAS is CPU and memory limited and already runs Gitea, the CI runner, QA and production; Portainer manages the stacks; cost should stay at zero.
| Tool | Fit |
| --- | --- |
| **Flipt** | Lightest — single binary, no heavy datastore. The only one that obviously fits this machine. |
| **Flagsmith** | Open source, self-host free with no feature limits, Docker Compose. Heavier than Flipt. |
| **GrowthBook** | Free self-hosted and a free cloud starter. Flags and experiments together, which is more than this project needs. |
| **Unleash** | Best known, but **OSS Edge sunsets 2026-12-31**, after which self-hosters at scale need Enterprise Edge. A free tool acquiring a paid dependency inside a year is the wrong shape here. |
A hosted free tier is also possible and is worse for this specific project: it makes the storefront depend on a third party being reachable in order to decide its own behaviour, which is a new outage mode for a site that currently has none.
**Whatever is chosen, fail-safe behaviour matters more than features.** A flag service that takes the shop down when it is unreachable is worse than having no flags. `admin_settings` reads from the database the application already cannot run without, so it has no failure mode of its own — which is a real advantage over every option in that table.
## What would change this answer
Any one of these, and it is worth reopening:
- A concrete change that genuinely needs to be dark in production while it settles — passkey login being the likely first.
- Percentage rollouts or per-customer targeting becoming something anyone actually wants. Nothing in the project has ever asked for either.
- More than one person deploying, so "who turned that off" needs an audit trail.
- CD arriving, which would make decoupling deploy from release mean something.
Until one of those, the answer is a boolean column type in `admin_settings`, written when there is a flag to put in it.