export type InvitationDisplayState = "active" | "expired" | "revoked"; export function deriveInvitationDisplayState( invitation: { state: string; ends_at: string | null; revoked_at: string | null }, now = new Date().toISOString(), ): InvitationDisplayState { if (invitation.state === "revoked" || invitation.revoked_at) return "revoked"; if (invitation.ends_at && invitation.ends_at <= now) return "expired"; return "active"; } /** * Create the replay record only while the requested administrator invitation * is still revocable. D1 batch serialization makes this predicate the fence * for concurrent revocations with different idempotency keys. * * Parameters: scope, key, request hash, response JSON, expiry, now, grant id. */ export const CREATE_INVITATION_REVOCATION_IDEMPOTENCY_SQL = ` INSERT INTO idempotency_records (scope, key, request_hash, response_json, status_code, expires_at, created_at) SELECT ?1, ?2, ?3, ?4, 200, ?5, ?6 FROM entitlement_grants WHERE id = ?7 AND source = 'admin_exemption' AND state = 'active' AND revoked_at IS NULL `; /** Parameters: revoked time, grant id, scope, key, request hash. */ export const REVOKE_INVITATION_SQL = ` UPDATE entitlement_grants SET state = 'revoked', revoked_at = ?1 WHERE id = ?2 AND source = 'admin_exemption' AND state = 'active' AND revoked_at IS NULL AND EXISTS ( SELECT 1 FROM idempotency_records WHERE scope = ?3 AND key = ?4 AND request_hash = ?5 ) `; /** * Append exactly one audit row for a successful revocation. The audit id is * deterministic per idempotency key, so a replay cannot duplicate history. */ export const AUDIT_INVITATION_REVOCATION_SQL = ` INSERT OR IGNORE INTO audit_events (id, actor_id, action, target_type, target_id, reason, before_json, after_json, correlation_id, created_at) SELECT ?1, ?2, 'entitlement.invitation_revoked', 'entitlement_grant', ?3, ?4, ?5, ?6, ?7, ?8 WHERE EXISTS ( SELECT 1 FROM idempotency_records WHERE scope = ?9 AND key = ?10 AND request_hash = ?11 ) AND EXISTS ( SELECT 1 FROM entitlement_grants WHERE id = ?3 AND state = 'revoked' AND revoked_at = ?8 ) `;