Preview grants

Registered testers can reach named preview features through REST and MCP using signed grants bound to their authenticated session. Generic job and email producers receive a serializable release context from enqueueReleaseWork; they must persist it with the work and pass it to executeReleaseWork, which rechecks expiry, revocation and live tester membership before the effect. Concrete production workers and emailers do not yet declare a preview surface. Machine-credential minting remains unavailable. A verified grant does not replace platform switches, org entitlements or ordinary permissions.

A verified platform admin can register a tester with POST /v1/admin/preview-testers, using {subjectType: "user" | "org" | "service", subjectId, reason}. Remove registration with DELETE /v1/admin/preview-testers/:subjectType/:subjectId. User registration matches the authenticated user; org registration matches a current membership, rechecked at each verification. Service entries are inert in this increment. Project role tester is unrelated. Registry changes are audited.

POST /v1/preview-grants accepts {features: ["feature.key"], ttlSeconds?: number}. All names must exist in the release inventory and have an explicit preview authorization. TTL is configurable per mint, defaults to four hours, and is limited to twelve hours. The grant is bound to the authenticated user and exact session, named features, issue/expiry times and a random grant id. The response contains {grant, grantId, expiresAt} with Cache-Control: private, no-store. It also sets gq_preview with HttpOnly, Secure, SameSite=Lax and Path=/.

Send the returned token using X-GoodQA-Preview or the secure cookie. Query parameters are never consulted. A header takes precedence over a cookie. Each supplied token is verified before a request handler runs: HMAC-SHA256, identity/session equality, stored grant equality, expiry, revocation and current tester membership. Invalid tokens return 404 on release-protected routes and 403 at the MCP transport and other authenticated routes; they clear the preview cookie and append an audit refusal. The request receives only {grantId, features} as req.preview; the release guard re-reads the grant and checks its named feature before granting access. No cookie/header means no preview annotation. An ordinary non-admin session can enter /v1/mcp only with a verified preview grant. Its tool flags come from its own org membership and the existing feature checks; admin-only tools remain unavailable. Each tool invocation re-enters authentication and release authorization. Requests with an invalid login credential still fail normal authentication.

DELETE /v1/preview-grants/:id revokes a grant belonging to the authenticated user. It is idempotent for an already-revoked owned grant and returns 404 for someone else's grant. Revocation and mint events are transactional with the associated write. First successful use is recorded once per grant per surface, with a partial unique index arbitrating concurrent first uses. Audit stores no raw grant, cookie or signature.

Signing uses the existing JWT_SECRET secret injection with an explicit preview-purpose prefix. Rotating that secret invalidates every old preview signature immediately, as well as the existing credentials that depend on it. There is no grace key in this increment. Persisted grants alone cannot recreate a valid signature.

No testers are seeded by the migration. No production calls or deployment are part of author acceptance. See the ops document for the post-deploy registration requests.

Implementation references: Node HMAC and constant-time comparison, Fastify request lifecycle hooks.

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