Release authorization

Release authorization is an additional server-side ceiling for explicitly registered features. Access requires all four terms: release authorization, platform setting, organization entitlement, and the caller's normal permission. A tenant entitlement override cannot enable a release that is off.

packages/shared/src/release-features.ts maps each key to its platform setting, entitlement, permission, and adopted surfaces. Unknown keys deny access. A new registered key without a database row defaults to off. Verified existing behavior can explicitly set grandfathered: true, which defaults to on; its baseline description is not definition-of-done evidence.

States are off, preview, and on. Preview denies every identity in this slice: previewAllows(ctx, featureKey) is the sole audience decision seam for the subsequent signed preview-grants implementation. An audience JSON value, cookie, flag, or client-supplied identity does not authorize preview access.

Server boundaries

  • API routes opt in with requireRelease(featureKey) after authentication and before handler effects. Denials return 404. Existing permission checks remain.
  • MCP tools opt in through the registry's mcpTools inventory. The production curated registrar prepares their authorization before connecting the SDK, omits denied tools from discovery, and rechecks direct invocations. Its returned synchronous names array remains the complete metadata inventory. Registration-only documentation recorders receive every tool without authenticating or connecting. A previously connected tool observes revocation on invocation; discovery is prepared per connection, so reconnect to discover newly enabled tools.
  • Job and email producers enclose the enqueue/send callback in enqueueReleaseWork. Workers enclose the actual effect in executeReleaseWork, which checks current policy even for previously approved queued work. Persist identity and feature key with work, never a cached allow.
  • Server pages/actions enclose rendering or effects in requireReleasePage. Its authorization callback must obtain a fresh authenticated server decision, forwarding the current principal without caching. Denials invoke Next's 404; authorization errors propagate before rendering.

These helpers require adoption at actual effect boundaries. This infrastructure does not automatically guard all existing routes, pages, jobs, or emails. The initial MCP adoption covers crm_reports_list and crm_report_run under crm.reports, grandfathered as existing live behavior. Missing authorization keeps CRM reports available; explicit off/preview still denies adopted MCP surfaces and the server reports page. REST darkness is proved with a NEW test fixture, preserving shipped CRM REST behavior. The reports page now calls requireReleasePage before rendering its unchanged client component. Its server adapter forwards the current cookie and authorization header to GET /v1/release-authorizations/:featureKey/decision?orgSlug=.... This session-only endpoint returns { allowed: boolean } using the authenticated user, the selected organization's current policy, and all four access terms. Both the HTTP response and server fetch disable caching; failed decisions stop rendering. The admin policy endpoint is never used as an access decision. The transition slice owns further surface adoption. Existing getPlatformSetting, isFeatureEnabled, and requireFeature callers retain their behavior.

Administration and audit

Verified platform administrators use authenticated GET /v1/admin/release-authorizations/:featureKey and PUT /v1/admin/release-authorizations/:featureKey. Unknown keys return 404. GET returns { authorization }; a missing row reports version 0 and its effective default state. PUT accepts state, audience (object, default {}), version, and a nonblank reason. Send the last observed version; success returns the incremented version in { authorization }. Stale writes return 409. The transaction serializes even concurrent first writes for the same feature.

Migration 13599_release_authorizations.sql adds the state enum, authorization table, and events table. A database trigger appends an event for each insert or update in the same transaction, including direct SQL writers. Audit failure rolls the policy write back. Updates and deletes of audit events are refused. Database owners can still alter triggers or truncate tables; this is application audit protection, not a boundary against database administrators.

The migration is additive. Disable a feature by writing off; rollback does not require dropping tables or deleting audit history. Turning a feature off prevents subsequent guarded execution but cannot undo an effect already sent.

We use essential cookies to keep you signed in. See our Privacy Policy for details.