{"slug": "dev-log-2026-07-07", "title": "Dev Log: 2026-07-07", "summary": "A developer consolidated four repositories by eliminating duplicate sources of truth. They migrated a SaaS from per-feature subscriptions to plans-only entitlements without billing outage, unified authorization across web, API, and MCP surfaces, fixed a password reset column mismatch in a legacy Oracle schema, and centralized plan-tier flags to prevent drift between billing and pricing configs.", "body_md": "Four repos moved today. Here's the thread that ties most of them together: **one source of truth beats two.**\n\nSpent 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.\n\nAn 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.\n\nThe fix was to make the MCP tools gate on the **same permission layer** as everything else, so:\n\n```\n  web  ─┐\n  API  ─┼─►  one permission check  ─►  allow / deny\n  MCP  ─┘\n```\n\nTL;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.\n\n| Before | After |\n|---|---|\n| Each surface authorizes its own way | Single permission check, shared |\n| MCP tool could out-permission the UI | MCP bound to the same guard |\n| No debug-scoped role |\n`support_engineer` role, read-only diagnostics |\n\nSmaller but sharp: a password reset was writing to the wrong Oracle column and also touching a `date_modified`\n\nfield 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.\n\nA 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.\n\nThree 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.", "url": "https://wpnews.pro/news/dev-log-2026-07-07", "canonical_source": "https://dev.to/nasrulhazim/dev-log-2026-07-07-3jc2", "published_at": "2026-07-07 21:48:06+00:00", "updated_at": "2026-07-07 21:58:19.046213+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "ai-infrastructure"], "entities": ["Oracle", "MCP"], "alternates": {"html": "https://wpnews.pro/news/dev-log-2026-07-07", "markdown": "https://wpnews.pro/news/dev-log-2026-07-07.md", "text": "https://wpnews.pro/news/dev-log-2026-07-07.txt", "jsonld": "https://wpnews.pro/news/dev-log-2026-07-07.jsonld"}}