TL;DR
- Collapsed a billing model from à-la-carte features to plans-only, in four safe phases.
- Unified authorization across web, API, and MCP so all three obey one permission layer.
- Fixed a legacy Oracle password-sync writing to the wrong column.
Four repos moved today. Here's the thread that ties most of them together: one source of truth beats two.
Billing: plans-only
Spent most of the day migrating a SaaS off per-feature à-la-carte subscriptions and onto plans-only entitlements. The interesting part isn't the model — it's doing it without a billing outage: seed plans, switch reads to plans, backfill every org, then delete the old machinery. Expand/contract, four deployable phases. Full write-up in the focused post.
One permission layer for three surfaces
An ops tool exposed the same actions three ways — web UI, API, and an MCP server for agent access. The bug: each surface checked authorization slightly differently, so an MCP tool could allow something the web UI blocked.
The fix was to make the MCP tools gate on the same permission layer as everything else, so:
web ─┐
API ─┼─► one permission check ─► allow / deny
MCP ─┘
TL;DR: web ≡ API ≡ MCP — three doors, one lock. Also added a dedicated support-engineer role scoped for debugging without handing over the keys to everything, plus identity/diagnostics/SLA read tools so an agent can answer "why didn't this notification send?" without shell access.
| Before | After |
|---|---|
| Each surface authorizes its own way | Single permission check, shared |
| MCP tool could out-permission the UI | MCP bound to the same guard |
| No debug-scoped role |
support_engineer role, read-only diagnostics |
Legacy Oracle password sync
Smaller but sharp: a password reset was writing to the wrong Oracle column and also touching a date_modified field it had no business updating. Routed the student reset to the correct password column and dropped the stray write. Lesson with legacy schemas — the column that looks right and the column the app actually reads from are not always the same. Confirm against the read path, not the name.
Pricing config as one source of truth
A one-line fix, but a good reminder: a plan-tier flag (which tier unlocks a given capability) had drifted between the billing logic and the public pricing page. When entitlement rules live in two places, they will disagree. Keep the tier matrix in one config and read it everywhere.
Takeaway
Three of today's four changes are the same idea wearing different clothes: remove the second source of truth. Two entitlement models → one plan. Three authorization paths → one permission layer. Two pricing configs → one. Duplication in rules is worse than duplication in code, because the copies drift silently until someone gets billed wrong or let in where they shouldn't be.
Top comments (0)