Background
Proposed for v1.3. AccessFlow approves individual queries and supports admin-set static expiry on grants (permissions.expires_at), but there is no self-service workflow for a user to request temporary, scoped access (e.g. "write to analytics for 4 hours") that is granted on approval and automatically revoked on expiry. Just-in-time access is the defining capability of the JIT/PAM category (StrongDM, HashiCorp Boundary/Vault, Satori, ConductorOne, Opal) and is the single largest gap between AccessFlow and that category.
Scope
Let users raise an access-grant request that flows through the existing approval engine and, once approved, materialises as a time-boxed permissions row that a scheduled job revokes on expiry.
- New table
access_grant_request (Flyway V56+): id UUID PK, organization_id, requester_id, datasource_id, requested capabilities (can_read/can_write/can_ddl), optional allowed_schemas/allowed_tables, duration (ISO-8601), justification, status (snake_case enum access_grant_status: PENDING/APPROVED/REJECTED/EXPIRED/REVOKED/CANCELLED), expires_at, granted_permission_id NULL, timestamps. UUID PK, TIMESTAMPTZ.
- Approval reuses the existing reviewer-eligibility + self-approval-block logic — a requester can never approve their own request. Routed through the same
review_plans / datasource_reviewers machinery the query review queue uses.
- On final approval, the permission service writes a
permissions row with expires_at = now + duration; on rejection/cancel nothing is granted.
- New
scheduling job AccessGrantExpiryJob (mirror QueryTimeoutJob): scans for grants past expires_at, revokes them, writes audit + notification. @SchedulerLock (ShedLock/Redis), ISO-8601 fixedDelayString cadence property under accessflow.security.*, per the CLAUDE.md scheduled-job rules.
- REST:
POST /api/v1/access-requests, GET /api/v1/access-requests (mine), GET /admin/access-requests (review queue), POST /admin/access-requests/{id}/{approve,reject}, DELETE /api/v1/access-requests/{id} (requester cancel). Paginated per docs/04-api-spec.md.
- Audit: new actions
ACCESS_REQUEST_SUBMITTED/APPROVED/REJECTED/CANCELLED, ACCESS_GRANT_EXPIRED/REVOKED.
- Notifications: reuse the dispatcher fanout (reviewers on submit; requester on decision/expiry) + WebSocket
access_request.* events.
- Frontend: a "Request access" page + an admin "Access requests" queue alongside
ReviewQueuePage; surface active time-boxed grants and remaining TTL on DatasourceSettingsPage.
Acceptance criteria
Pointers
Background
Proposed for v1.3. AccessFlow approves individual queries and supports admin-set static expiry on grants (
permissions.expires_at), but there is no self-service workflow for a user to request temporary, scoped access (e.g. "write toanalyticsfor 4 hours") that is granted on approval and automatically revoked on expiry. Just-in-time access is the defining capability of the JIT/PAM category (StrongDM, HashiCorp Boundary/Vault, Satori, ConductorOne, Opal) and is the single largest gap between AccessFlow and that category.Scope
Let users raise an access-grant request that flows through the existing approval engine and, once approved, materialises as a time-boxed
permissionsrow that a scheduled job revokes on expiry.access_grant_request(FlywayV56+):id UUID PK,organization_id,requester_id,datasource_id, requested capabilities (can_read/can_write/can_ddl), optionalallowed_schemas/allowed_tables,duration(ISO-8601),justification,status(snake_case enumaccess_grant_status:PENDING/APPROVED/REJECTED/EXPIRED/REVOKED/CANCELLED),expires_at,granted_permission_id NULL, timestamps. UUID PK,TIMESTAMPTZ.review_plans/datasource_reviewersmachinery the query review queue uses.permissionsrow withexpires_at = now + duration; on rejection/cancel nothing is granted.schedulingjobAccessGrantExpiryJob(mirrorQueryTimeoutJob): scans for grants pastexpires_at, revokes them, writes audit + notification.@SchedulerLock(ShedLock/Redis), ISO-8601fixedDelayStringcadence property underaccessflow.security.*, per the CLAUDE.md scheduled-job rules.POST /api/v1/access-requests,GET /api/v1/access-requests(mine),GET /admin/access-requests(review queue),POST /admin/access-requests/{id}/{approve,reject},DELETE /api/v1/access-requests/{id}(requester cancel). Paginated per docs/04-api-spec.md.ACCESS_REQUEST_SUBMITTED/APPROVED/REJECTED/CANCELLED,ACCESS_GRANT_EXPIRED/REVOKED.access_request.*events.ReviewQueuePage; surface active time-boxed grants and remaining TTL onDatasourceSettingsPage.Acceptance criteria
V56+(new table + enum; columns nullable or defaulted).permissionsrow withexpires_at;AccessGrantExpiryJobrevokes on expiry with@SchedulerLock+ audit.messages.properties+en.json); i18n parity tests pass.ApplicationModulesTest+ApiPackageDependencyTestgreen.e2e/tests/: request → approve → grant visible → (expiry) revoke.website/updated.Pointers
QueryTimeoutJob, ShedLock): docs/05-backend.mdfrontend/src/pages/reviews/