import type { FastifyRequest, FastifyReply } from 'fastify'; import type { DataStore, SessionClaims, OrgRole } from '@openleash/core'; /** * The "scope" a user is acting within in the GUI. `personal` means the user's * own agents/policies; `org` means an organization the user belongs to. * * Scope is surfaced in the sidebar switcher and in URL structure. Phase 2 adds * the switcher and slug-based aliases; Phase 3 will flip all owner GUI routes * to be scoped (`/gui/personal/*`, `/gui/orgs/:slug/*`). */ export type Scope = { type: 'user'; id: string; display_name: string; } | { type: 'org'; id: string; slug: string; display_name: string; role: OrgRole; }; export interface AvailableScopes { current: Scope; available: Scope[]; /** True when the user can switch to an org scope. Used to hide the switcher for solo users. */ hasOrgs: boolean; } /** * Encode a scope as a cookie value. Personal → `personal`; org → `org:`. * Slug is a stable identifier under user control, so it's safe to expose in the * cookie rather than an opaque org_id. */ export declare function encodeScopeCookie(scope: Scope): string; /** * Decode the last_scope cookie into either `{ type: 'personal' }` or * `{ type: 'org', slug }`. The caller is responsible for resolving that slug * against the store (the cookie value may be stale after a rename). */ export declare function decodeScopeCookie(value: string | undefined | null): { type: 'personal'; } | { type: 'org'; slug: string; } | null; export declare function readLastScopeCookie(request: FastifyRequest): string | null; export declare function writeLastScopeCookie(reply: FastifyReply, scope: Scope): void; /** * Compute the list of scopes this user can act within. Always includes * personal; adds every org the user is an active member of. Orgs missing a * slug (shouldn't happen post-migration) are silently skipped. */ export declare function buildAvailableScopes(store: DataStore, session: SessionClaims): { personal: Scope; orgs: Scope[]; }; /** * Determine the current scope for a request. Resolution order: * 1. URL pattern: `/gui/orgs/:slug/*` → that org (if user is a member). * 2. URL pattern: `/gui/organizations/:orgId/*` → that org (if user is a member). * 3. last_scope cookie, if it points to a scope the user still has access to. * 4. Personal. * * Returns `null` only when the session is entirely invalid (user record gone). */ export declare function resolveCurrentScope(store: DataStore, session: SessionClaims, request: FastifyRequest): AvailableScopes | null; /** * Convert a scope into the `{ owner_type, owner_id }` shape used by data * filters throughout the codebase (agents/policies/audit are all keyed on * that pair via `OwnerType | 'user' | 'org'`). */ export declare function scopeOwner(scope: Scope): { ownerType: 'user' | 'org'; ownerId: string; }; /** * Same as `scopeOwner`, but takes an `AvailableScopes` (what handlers get back * from `resolveCurrentScope`) so callers don't need to drill into `.current`. */ export declare function currentOwner(resolved: AvailableScopes): { ownerType: 'user' | 'org'; ownerId: string; }; /** * Count pending approval requests across every scope a user can act within. * Used by the sidebar bell badge and the cross-scope inbox. Cheap to call on * every owner page render — it reads the in-memory state index, not files. */ export declare function countPendingApprovalsAcrossScopes(store: DataStore, session: SessionClaims): number; export interface PendingScopeGroup { scope: Scope; approvalRequestIds: string[]; } /** * Walk every scope the user can act within and return pending approval IDs * grouped by scope. The inbox page uses this to render a sectioned list; * empty scopes are omitted. */ export declare function listPendingApprovalsByScope(store: DataStore, session: SessionClaims): PendingScopeGroup[]; /** * PreHandler for routes under `/gui/orgs/:slug/*`. Validates the slug and * membership, and redirects historical slugs to the current one so we always * render on the canonical URL. * * 404s if: * - the slug doesn't resolve to any org (never seen or corrupted state), or * - the user isn't an active member of the resolved org. * 302s if the slug is historical (lives only in `slug_history`). */ export declare function createOrgScopePreHandler(store: DataStore): (request: FastifyRequest, reply: FastifyReply) => Promise; //# sourceMappingURL=scope.d.ts.map